Skip to content

Commit 1b2159a

Browse files
authored
[React Native] measure calls will now call FabricUIManager (#15324)
* [React Native] Add tests to paper renderer for measure, measureLayout * [React Native] measure calls will now call FabricUIManager The Fabric renderer was previously calling the paper UIManager's measure calls and passing the react tag. This PR changes the renderer to now call FabricUIManager passing the node instead. One of the parts of this that feels more controversial is making NativeMethodsMixin and ReactNative.NativeComponent warn when calling measureLayout in Fabric. As Seb and I decided in #15126, it doesn't make sense for a component created with one of these methods to require a native ref but not work the other way around. For example: a.measureLayout(b) might work but b.measureLayout(a) wouldn't. We figure we should keep these consistent and continue migrating things off of NativeMethodsMixin and NativeComponent. If this becomes problematic for the Fabric rollout then we should revisit this. * Fixing Flow * Add FabricUIManager to externals for paper renderer * import * as FabricUIManager from 'FabricUIManager'; * Update tests * Shouldn't have removed UIManager import * Update with the new tests
1 parent c7a9599 commit 1b2159a

File tree

8 files changed

+453
-74
lines changed

8 files changed

+453
-74
lines changed

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

Lines changed: 106 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type {
1818
import invariant from 'shared/invariant';
1919
// Modules provided by RN:
2020
import TextInputState from 'TextInputState';
21+
import * as FabricUIManager from 'FabricUIManager';
2122
import UIManager from 'UIManager';
2223

2324
import {create} from './ReactNativeAttributePayload';
@@ -68,10 +69,33 @@ export default function(
6869
* prop](docs/view.html#onlayout) instead.
6970
*/
7071
measure: function(callback: MeasureOnSuccessCallback) {
71-
UIManager.measure(
72-
findNodeHandle(this),
73-
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
74-
);
72+
let maybeInstance;
73+
74+
// Fiber errors if findNodeHandle is called for an umounted component.
75+
// Tests using ReactTestRenderer will trigger this case indirectly.
76+
// Mimicking stack behavior, we should silently ignore this case.
77+
// TODO Fix ReactTestRenderer so we can remove this try/catch.
78+
try {
79+
maybeInstance = findHostInstance(this);
80+
} catch (error) {}
81+
82+
// If there is no host component beneath this we should fail silently.
83+
// This is not an error; it could mean a class component rendered null.
84+
if (maybeInstance == null) {
85+
return;
86+
}
87+
88+
if (maybeInstance.canonical) {
89+
FabricUIManager.measure(
90+
maybeInstance.node,
91+
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
92+
);
93+
} else {
94+
UIManager.measure(
95+
findNodeHandle(this),
96+
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
97+
);
98+
}
7599
},
76100

77101
/**
@@ -90,10 +114,33 @@ export default function(
90114
* has been completed in native.
91115
*/
92116
measureInWindow: function(callback: MeasureInWindowOnSuccessCallback) {
93-
UIManager.measureInWindow(
94-
findNodeHandle(this),
95-
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
96-
);
117+
let maybeInstance;
118+
119+
// Fiber errors if findNodeHandle is called for an umounted component.
120+
// Tests using ReactTestRenderer will trigger this case indirectly.
121+
// Mimicking stack behavior, we should silently ignore this case.
122+
// TODO Fix ReactTestRenderer so we can remove this try/catch.
123+
try {
124+
maybeInstance = findHostInstance(this);
125+
} catch (error) {}
126+
127+
// If there is no host component beneath this we should fail silently.
128+
// This is not an error; it could mean a class component rendered null.
129+
if (maybeInstance == null) {
130+
return;
131+
}
132+
133+
if (maybeInstance.canonical) {
134+
FabricUIManager.measureInWindow(
135+
maybeInstance.node,
136+
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
137+
);
138+
} else {
139+
UIManager.measureInWindow(
140+
findNodeHandle(this),
141+
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
142+
);
143+
}
97144
},
98145

99146
/**
@@ -105,16 +152,60 @@ export default function(
105152
* `findNodeHandle(component)`.
106153
*/
107154
measureLayout: function(
108-
relativeToNativeNode: number,
155+
relativeToNativeNode: number | Object,
109156
onSuccess: MeasureLayoutOnSuccessCallback,
110157
onFail: () => void /* currently unused */,
111158
) {
112-
UIManager.measureLayout(
113-
findNodeHandle(this),
114-
relativeToNativeNode,
115-
mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
116-
mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
117-
);
159+
let maybeInstance;
160+
161+
// Fiber errors if findNodeHandle is called for an umounted component.
162+
// Tests using ReactTestRenderer will trigger this case indirectly.
163+
// Mimicking stack behavior, we should silently ignore this case.
164+
// TODO Fix ReactTestRenderer so we can remove this try/catch.
165+
try {
166+
maybeInstance = findHostInstance(this);
167+
} catch (error) {}
168+
169+
// If there is no host component beneath this we should fail silently.
170+
// This is not an error; it could mean a class component rendered null.
171+
if (maybeInstance == null) {
172+
return;
173+
}
174+
175+
if (maybeInstance.canonical) {
176+
warningWithoutStack(
177+
false,
178+
'Warning: measureLayout on components using NativeMethodsMixin ' +
179+
'or ReactNative.NativeComponent is not currently supported in Fabric. ' +
180+
'measureLayout must be called on a native ref. Consider using forwardRef.',
181+
);
182+
return;
183+
} else {
184+
let relativeNode;
185+
186+
if (typeof relativeToNativeNode === 'number') {
187+
// Already a node handle
188+
relativeNode = relativeToNativeNode;
189+
} else if (relativeToNativeNode._nativeTag) {
190+
relativeNode = relativeToNativeNode._nativeTag;
191+
}
192+
193+
if (relativeNode == null) {
194+
warningWithoutStack(
195+
false,
196+
'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.',
197+
);
198+
199+
return;
200+
}
201+
202+
UIManager.measureLayout(
203+
findNodeHandle(this),
204+
relativeNode,
205+
mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
206+
mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
207+
);
208+
}
118209
},
119210

120211
/**

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

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ import {
3939
appendChildToSet as appendChildNodeToSet,
4040
completeRoot,
4141
registerEventHandler,
42+
measure as fabricMeasure,
43+
measureInWindow as fabricMeasureInWindow,
44+
measureLayout as fabricMeasureLayout,
4245
} from 'FabricUIManager';
43-
import UIManager from 'UIManager';
4446

4547
// Counter for uniquely identifying views.
4648
// % 10 === 1 means it is a rootTag.
@@ -85,15 +87,18 @@ class ReactFabricHostComponent {
8587
_nativeTag: number;
8688
viewConfig: ReactNativeBaseComponentViewConfig<>;
8789
currentProps: Props;
90+
_internalInstanceHandle: Object;
8891

8992
constructor(
9093
tag: number,
9194
viewConfig: ReactNativeBaseComponentViewConfig<>,
9295
props: Props,
96+
internalInstanceHandle: Object,
9397
) {
9498
this._nativeTag = tag;
9599
this.viewConfig = viewConfig;
96100
this.currentProps = props;
101+
this._internalInstanceHandle = internalInstanceHandle;
97102
}
98103

99104
blur() {
@@ -105,15 +110,15 @@ class ReactFabricHostComponent {
105110
}
106111

107112
measure(callback: MeasureOnSuccessCallback) {
108-
UIManager.measure(
109-
this._nativeTag,
113+
fabricMeasure(
114+
this._internalInstanceHandle.stateNode.node,
110115
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
111116
);
112117
}
113118

114119
measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
115-
UIManager.measureInWindow(
116-
this._nativeTag,
120+
fabricMeasureInWindow(
121+
this._internalInstanceHandle.stateNode.node,
117122
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
118123
);
119124
}
@@ -123,32 +128,21 @@ class ReactFabricHostComponent {
123128
onSuccess: MeasureLayoutOnSuccessCallback,
124129
onFail: () => void /* currently unused */,
125130
) {
126-
let relativeNode;
127-
128-
if (typeof relativeToNativeNode === 'number') {
129-
// Already a node handle
130-
relativeNode = relativeToNativeNode;
131-
} else if (relativeToNativeNode._nativeTag) {
132-
relativeNode = relativeToNativeNode._nativeTag;
133-
} else if (
134-
relativeToNativeNode.canonical &&
135-
relativeToNativeNode.canonical._nativeTag
131+
if (
132+
typeof relativeToNativeNode === 'number' ||
133+
!(relativeToNativeNode instanceof ReactFabricHostComponent)
136134
) {
137-
relativeNode = relativeToNativeNode.canonical._nativeTag;
138-
}
139-
140-
if (relativeNode == null) {
141135
warningWithoutStack(
142136
false,
143-
'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.',
137+
'Warning: ref.measureLayout must be called with a ref to a native component.',
144138
);
145139

146140
return;
147141
}
148142

149-
UIManager.measureLayout(
150-
this._nativeTag,
151-
relativeNode,
143+
fabricMeasureLayout(
144+
this._internalInstanceHandle.stateNode.node,
145+
relativeToNativeNode._internalInstanceHandle.stateNode.node,
152146
mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
153147
mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
154148
);
@@ -212,7 +206,12 @@ export function createInstance(
212206
internalInstanceHandle, // internalInstanceHandle
213207
);
214208

215-
const component = new ReactFabricHostComponent(tag, viewConfig, props);
209+
const component = new ReactFabricHostComponent(
210+
tag,
211+
viewConfig,
212+
props,
213+
internalInstanceHandle,
214+
);
216215

217216
return {
218217
node: node,

0 commit comments

Comments
 (0)