Skip to content

Commit 051637d

Browse files
authored
Extract Fabric event handlers from canonical props (#13024)
We need a different "component tree" thingy for Fabric. A lot of this doesn't really make much sense in a persistent world but currently we can't dispatch events to memoizedProps on a Fiber since they're pooled. Also, it's unclear what the semantics should be when we dispatch an event that happened when the old props were in effect but now we have new props already. This implementation tries to use the last committed props but also fails at that because we don't have a commit hook in the persistent mode. However, at least it doesn't crash when dispatching. :)
1 parent 2a80859 commit 051637d

File tree

8 files changed

+85
-22
lines changed

8 files changed

+85
-22
lines changed

packages/react-native-renderer/src/ReactFabric.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import ReactVersion from 'shared/ReactVersion';
2020

2121
import NativeMethodsMixin from './NativeMethodsMixin';
2222
import ReactNativeComponent from './ReactNativeComponent';
23-
import * as ReactNativeComponentTree from './ReactNativeComponentTree';
23+
import * as ReactFabricComponentTree from './ReactFabricComponentTree';
2424
import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
2525

2626
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
@@ -120,7 +120,7 @@ const ReactFabric: ReactFabricType = {
120120
};
121121

122122
ReactFabricRenderer.injectIntoDevTools({
123-
findFiberByHostInstance: ReactNativeComponentTree.getClosestInstanceFromNode,
123+
findFiberByHostInstance: ReactFabricComponentTree.getClosestInstanceFromNode,
124124
getInspectorDataForViewTag: getInspectorDataForViewTag,
125125
bundleType: __DEV__ ? 1 : 0,
126126
version: ReactVersion,
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright (c) 2013-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import invariant from 'fbjs/lib/invariant';
9+
10+
function getInstanceFromInstance(instanceHandle) {
11+
return instanceHandle;
12+
}
13+
14+
function getTagFromInstance(inst) {
15+
let tag = inst.stateNode.canonical._nativeTag;
16+
invariant(tag, 'All native instances should have a tag.');
17+
return tag;
18+
}
19+
20+
export {
21+
getInstanceFromInstance as getClosestInstanceFromNode,
22+
getInstanceFromInstance as getInstanceFromNode,
23+
getTagFromInstance as getNodeFromInstance,
24+
};
25+
26+
export function getFiberCurrentPropsFromNode(inst) {
27+
return inst.canonical.currentProps;
28+
}

packages/react-native-renderer/src/ReactFabricHostConfig.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ export function prepareUpdate(
293293
);
294294
// TODO: If the event handlers have changed, we need to update the current props
295295
// in the commit phase but there is no host config hook to do it yet.
296+
// So instead we hack it by updating it in the render phase.
297+
instance.canonical.currentProps = newProps;
296298
return updatePayload;
297299
}
298300

packages/react-native-renderer/src/ReactFabricInjection.js

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,7 @@
99

1010
import './ReactNativeInjectionShared';
1111

12-
// TODO: The event emitter registration is interfering with the existing
13-
// ReactNative renderer. So disable it for Fabric for now.
12+
import * as ReactFabricComponentTree from './ReactFabricComponentTree';
13+
import * as EventPluginUtils from 'events/EventPluginUtils';
1414

15-
// import * as ReactNativeEventEmitter from './ReactNativeEventEmitter';
16-
17-
// Module provided by RN:
18-
// import RCTEventEmitter from 'RCTEventEmitter';
19-
20-
/**
21-
* Register the event emitter with the native bridge
22-
*/
23-
// RCTEventEmitter.register(ReactNativeEventEmitter);
15+
EventPluginUtils.injection.injectComponentTree(ReactFabricComponentTree);

packages/react-native-renderer/src/ReactNativeComponentTree.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,7 @@ export function uncacheFiberNode(tag) {
2020
}
2121

2222
function getInstanceFromTag(tag) {
23-
if (typeof tag === 'number') {
24-
return instanceCache[tag] || null;
25-
} else {
26-
// Fabric will invoke event emitters on a direct fiber reference
27-
return tag;
28-
}
23+
return instanceCache[tag] || null;
2924
}
3025

3126
function getTagFromInstance(inst) {

packages/react-native-renderer/src/ReactNativeInjection.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import './ReactNativeInjectionShared';
1111

12+
import * as ReactNativeComponentTree from './ReactNativeComponentTree';
13+
import * as EventPluginUtils from 'events/EventPluginUtils';
1214
import * as ReactNativeEventEmitter from './ReactNativeEventEmitter';
1315

1416
// Module provided by RN:
@@ -18,3 +20,5 @@ import RCTEventEmitter from 'RCTEventEmitter';
1820
* Register the event emitter with the native bridge
1921
*/
2022
RCTEventEmitter.register(ReactNativeEventEmitter);
23+
24+
EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree);

packages/react-native-renderer/src/ReactNativeInjectionShared.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,16 @@
1717
import 'InitializeCore';
1818

1919
import * as EventPluginHub from 'events/EventPluginHub';
20-
import * as EventPluginUtils from 'events/EventPluginUtils';
2120
import ResponderEventPlugin from 'events/ResponderEventPlugin';
2221

2322
import ReactNativeBridgeEventPlugin from './ReactNativeBridgeEventPlugin';
24-
import * as ReactNativeComponentTree from './ReactNativeComponentTree';
2523
import ReactNativeEventPluginOrder from './ReactNativeEventPluginOrder';
2624
import ReactNativeGlobalResponderHandler from './ReactNativeGlobalResponderHandler';
2725

2826
/**
2927
* Inject module for resolving DOM hierarchy and plugin ordering.
3028
*/
3129
EventPluginHub.injection.injectEventPluginOrder(ReactNativeEventPluginOrder);
32-
EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree);
3330

3431
ResponderEventPlugin.injection.injectGlobalResponderHandler(
3532
ReactNativeGlobalResponderHandler,

packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,4 +352,49 @@ describe('ReactFabric', () => {
352352
11,
353353
);
354354
});
355+
356+
it('dispatches events to the last committed props', () => {
357+
const View = createReactNativeComponentClass('RCTView', () => ({
358+
validAttributes: {},
359+
uiViewClassName: 'RCTView',
360+
directEventTypes: {
361+
topTouchStart: {
362+
registrationName: 'onTouchStart',
363+
},
364+
},
365+
}));
366+
367+
const touchStart = jest.fn();
368+
const touchStart2 = jest.fn();
369+
370+
ReactFabric.render(<View onTouchStart={touchStart} />, 11);
371+
372+
expect(FabricUIManager.createNode.mock.calls.length).toBe(1);
373+
expect(FabricUIManager.registerEventHandler.mock.calls.length).toBe(1);
374+
375+
let [, , , , instanceHandle] = FabricUIManager.createNode.mock.calls[0];
376+
let [dispatchEvent] = FabricUIManager.registerEventHandler.mock.calls[0];
377+
378+
let touchEvent = {
379+
touches: [],
380+
changedTouches: [],
381+
};
382+
383+
expect(touchStart).not.toBeCalled();
384+
385+
dispatchEvent(instanceHandle, 'topTouchStart', touchEvent);
386+
387+
expect(touchStart).toBeCalled();
388+
expect(touchStart2).not.toBeCalled();
389+
390+
ReactFabric.render(<View onTouchStart={touchStart2} />, 11);
391+
392+
// Intentionally dispatch to the same instanceHandle again.
393+
dispatchEvent(instanceHandle, 'topTouchStart', touchEvent);
394+
395+
// The current semantics dictate that we always dispatch to the last committed
396+
// props even though the actual scheduling of the event could have happened earlier.
397+
// This could change in the future.
398+
expect(touchStart2).toBeCalled();
399+
});
355400
});

0 commit comments

Comments
 (0)