Skip to content

Commit 108aed0

Browse files
authored
Fix use of stale props in Fabric events (#26408)
## Summary We had to revert the last React sync to React Native because we saw issues with Responder events using stale event handlers instead of recent versions. I reviewed the merged PRs and realized the problem was in the refactor I did in #26321. In that PR, we moved `currentProps` from `canonical`, which is a singleton referenced by all versions of the same fiber, to the fiber itself. This is causing the staleness we observed in events. This PR does a partial revert of the refactor in #26321, bringing back the `canonical` object but moving `publicInstance` to one of its fields, instead of being the `canonical` object itself. ## How did you test this change? Existing unit tests continue working (I didn't manage to get a repro using the test renderer). I manually tested this change in Meta infra and saw the problem was fixed.
1 parent 8fa41ff commit 108aed0

File tree

6 files changed

+47
-41
lines changed

6 files changed

+47
-41
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ import {getPublicInstance} from './ReactFabricHostConfig';
2222
function getInstanceFromNode(node: Instance | TextInstance): Fiber | null {
2323
const instance: Instance = (node: $FlowFixMe); // In React Native, node is never a text instance
2424

25-
if (instance.internalInstanceHandle != null) {
26-
return instance.internalInstanceHandle;
25+
if (
26+
instance.canonical != null &&
27+
instance.canonical.internalInstanceHandle != null
28+
) {
29+
return instance.canonical.internalInstanceHandle;
2730
}
2831

2932
// $FlowFixMe[incompatible-return] DevTools incorrectly passes a fiber in React Native.
@@ -41,7 +44,7 @@ function getNodeFromInstance(fiber: Fiber): PublicInstance {
4144
}
4245

4346
function getFiberCurrentPropsFromNode(instance: Instance): Props {
44-
return instance.currentProps;
47+
return instance.canonical.currentProps;
4548
}
4649

4750
export {

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

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,15 @@ export type Props = Object;
5555
export type Instance = {
5656
// Reference to the shadow node.
5757
node: Node,
58-
nativeTag: number,
59-
viewConfig: ViewConfig,
60-
currentProps: Props,
61-
// Reference to the React handle (the fiber)
62-
internalInstanceHandle: Object,
63-
// Exposed through refs.
64-
publicInstance: ReactFabricHostComponent,
58+
canonical: {
59+
nativeTag: number,
60+
viewConfig: ViewConfig,
61+
currentProps: Props,
62+
// Reference to the React handle (the fiber)
63+
internalInstanceHandle: Object,
64+
// Exposed through refs.
65+
publicInstance: ReactFabricHostComponent,
66+
},
6567
};
6668
export type TextInstance = {node: Node, ...};
6769
export type HydratableInstance = Instance | TextInstance;
@@ -148,11 +150,13 @@ export function createInstance(
148150

149151
return {
150152
node: node,
151-
nativeTag: tag,
152-
viewConfig,
153-
currentProps: props,
154-
internalInstanceHandle,
155-
publicInstance: component,
153+
canonical: {
154+
nativeTag: tag,
155+
viewConfig,
156+
currentProps: props,
157+
internalInstanceHandle,
158+
publicInstance: component,
159+
},
156160
};
157161
}
158162

@@ -222,8 +226,8 @@ export function getChildHostContext(
222226
}
223227

224228
export function getPublicInstance(instance: Instance): null | PublicInstance {
225-
if (instance.publicInstance != null) {
226-
return instance.publicInstance;
229+
if (instance.canonical != null && instance.canonical.publicInstance != null) {
230+
return instance.canonical.publicInstance;
227231
}
228232

229233
// For compatibility with the legacy renderer, in case it's used with Fabric
@@ -249,12 +253,12 @@ export function prepareUpdate(
249253
newProps: Props,
250254
hostContext: HostContext,
251255
): null | Object {
252-
const viewConfig = instance.viewConfig;
256+
const viewConfig = instance.canonical.viewConfig;
253257
const updatePayload = diff(oldProps, newProps, viewConfig.validAttributes);
254258
// TODO: If the event handlers have changed, we need to update the current props
255259
// in the commit phase but there is no host config hook to do it yet.
256260
// So instead we hack it by updating it in the render phase.
257-
instance.currentProps = newProps;
261+
instance.canonical.currentProps = newProps;
258262
return updatePayload;
259263
}
260264

@@ -333,11 +337,7 @@ export function cloneInstance(
333337
}
334338
return {
335339
node: clone,
336-
nativeTag: instance.nativeTag,
337-
viewConfig: instance.viewConfig,
338-
currentProps: instance.currentProps,
339-
internalInstanceHandle: instance.internalInstanceHandle,
340-
publicInstance: instance.publicInstance,
340+
canonical: instance.canonical,
341341
};
342342
}
343343

@@ -347,19 +347,15 @@ export function cloneHiddenInstance(
347347
props: Props,
348348
internalInstanceHandle: Object,
349349
): Instance {
350-
const viewConfig = instance.viewConfig;
350+
const viewConfig = instance.canonical.viewConfig;
351351
const node = instance.node;
352352
const updatePayload = create(
353353
{style: {display: 'none'}},
354354
viewConfig.validAttributes,
355355
);
356356
return {
357357
node: cloneNodeWithNewProps(node, updatePayload),
358-
nativeTag: instance.nativeTag,
359-
viewConfig: instance.viewConfig,
360-
currentProps: instance.currentProps,
361-
internalInstanceHandle: instance.internalInstanceHandle,
362-
publicInstance: instance.publicInstance,
358+
canonical: instance.canonical,
363359
};
364360
}
365361

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ function getInstanceFromTag(tag) {
2424
function getTagFromInstance(inst) {
2525
let nativeInstance = inst.stateNode;
2626
let tag = nativeInstance._nativeTag;
27-
if (tag === undefined) {
27+
if (tag === undefined && nativeInstance.canonical != null) {
2828
// For compatibility with Fabric
29-
tag = nativeInstance.nativeTag;
30-
nativeInstance = nativeInstance.publicInstance;
29+
tag = nativeInstance.canonical.nativeTag;
30+
nativeInstance = nativeInstance.canonical.publicInstance;
3131
}
3232

3333
if (!tag) {

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,11 @@ function getInspectorDataForViewAtPoint(
223223
}
224224

225225
closestInstance =
226-
internalInstanceHandle.stateNode.internalInstanceHandle;
226+
internalInstanceHandle.stateNode.canonical.internalInstanceHandle;
227227

228228
// Note: this is deprecated and we want to remove it ASAP. Keeping it here for React DevTools compatibility for now.
229-
const nativeViewTag = internalInstanceHandle.stateNode.nativeTag;
229+
const nativeViewTag =
230+
internalInstanceHandle.stateNode.canonical.nativeTag;
230231

231232
nativeFabricUIManager.measure(
232233
node,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ export function getChildHostContext(
218218

219219
export function getPublicInstance(instance: Instance): * {
220220
// $FlowExpectedError[prop-missing] For compatibility with Fabric
221-
if (instance.publicInstance != null) {
222-
return instance.publicInstance;
221+
if (instance.canonical != null && instance.canonical.publicInstance != null) {
222+
return instance.canonical.publicInstance;
223223
}
224224

225225
return instance;

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,12 @@ export function findHostInstance_DEPRECATED<TElementType: ElementType>(
5656
}
5757

5858
// For compatibility with Fabric instances
59-
if (componentOrHandle.publicInstance) {
59+
if (
60+
componentOrHandle.canonical &&
61+
componentOrHandle.canonical.publicInstance
62+
) {
6063
// $FlowExpectedError[incompatible-return] Can't refine componentOrHandle as a Fabric instance
61-
return componentOrHandle.publicInstance;
64+
return componentOrHandle.canonical.publicInstance;
6265
}
6366

6467
// For compatibility with legacy renderer instances
@@ -117,8 +120,11 @@ export function findNodeHandle(componentOrHandle: any): ?number {
117120
}
118121

119122
// For compatibility with Fabric instances
120-
if (componentOrHandle.nativeTag != null) {
121-
return componentOrHandle.nativeTag;
123+
if (
124+
componentOrHandle.canonical != null &&
125+
componentOrHandle.canonical.nativeTag != null
126+
) {
127+
return componentOrHandle.canonical.nativeTag;
122128
}
123129

124130
// For compatibility with Fabric public instances

0 commit comments

Comments
 (0)