ReactNative's ref.measureLayout now takes a ref (#15126)
* ReactNative's ref.measureLayout now takes a ref * Use Object as the additional param type * Remove unnecessary whitespace * Not supporting ref in mixin or subclass
Eli White committed
Mar 29, 2019 at 15:57 UTC
2e02469fa22d54b0a695037b82843eeaab56b364
5 files changed
+233
-4
packages/react-native-renderer/src/ReactFabricHostConfig.js
+25
-2
@@ -119,13 +119,36 @@ class ReactFabricHostComponent {
119
}
120
121
measureLayout(
122
- relativeToNativeNode: number,
122
+ relativeToNativeNode: number | Object,
123
onSuccess: MeasureLayoutOnSuccessCallback,
124
onFail: () => void /* currently unused */,
125
) {
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
136
+ ) {
137
+ relativeNode = relativeToNativeNode.canonical._nativeTag;
138
+ }
139
+
140
+ if (relativeNode == null) {
141
+ warningWithoutStack(
142
+ false,
143
+ 'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.',
144
+ );
145
+
146
+ return;
147
+ }
148
+
149
UIManager.measureLayout(
150
this._nativeTag,
128
- relativeToNativeNode,
151
+ relativeNode,
152
mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
153
mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
154
);
packages/react-native-renderer/src/ReactNativeFiberHostComponent.js
+25
-2
@@ -70,13 +70,36 @@ class ReactNativeFiberHostComponent {
70
}
71
72
measureLayout(
73
- relativeToNativeNode: number,
73
+ relativeToNativeNode: number | Object,
74
onSuccess: MeasureLayoutOnSuccessCallback,
75
onFail: () => void /* currently unused */,
76
) {
77
+ let relativeNode;
78
+
79
+ if (typeof relativeToNativeNode === 'number') {
80
+ // Already a node handle
81
+ relativeNode = relativeToNativeNode;
82
+ } else if (relativeToNativeNode._nativeTag) {
83
+ relativeNode = relativeToNativeNode._nativeTag;
84
+ } else if (
85
+ relativeToNativeNode.canonical &&
86
+ relativeToNativeNode.canonical._nativeTag
87
+ ) {
88
+ relativeNode = relativeToNativeNode.canonical._nativeTag;
89
+ }
90
+
91
+ if (relativeNode == null) {
92
+ warningWithoutStack(
93
+ false,
94
+ 'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.',
95
+ );
96
+
97
+ return;
98
+ }
99
+
100
UIManager.measureLayout(
101
this._nativeTag,
79
- relativeToNativeNode,
102
+ relativeNode,
103
mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
104
mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
105
);
packages/react-native-renderer/src/__mocks__/UIManager.js
+1
@@ -153,6 +153,7 @@ const RCTUIManager = {
153
views.get(parentTag).children.forEach(tag => removeChild(parentTag, tag));
154
}),
155
replaceExistingNonRootView: jest.fn(),
156
+ measureLayout: jest.fn(),
157
__takeSnapshot: jest.fn(),
158
};
159
packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
+55
@@ -302,6 +302,61 @@ describe('ReactFabric', () => {
302
});
303
});
304
305
+ it('should support ref in ref.measureLayout', () => {
306
+ const View = createReactNativeComponentClass('RCTView', () => ({
307
+ validAttributes: {foo: true},
308
+ uiViewClassName: 'RCTView',
309
+ }));
310
+
311
+ [View].forEach(Component => {
312
+ UIManager.measureLayout.mockReset();
313
+
314
+ let viewRef;
315
+ let otherRef;
316
+ ReactFabric.render(
317
+ <Component>
318
+ <Component
319
+ foo="bar"
320
+ ref={ref => {
321
+ viewRef = ref;
322
+ }}
323
+ />
324
+ <View
325
+ ref={ref => {
326
+ otherRef = ref;
327
+ }}
328
+ />
329
+ </Component>,
330
+ 11,
331
+ );
332
+
333
+ expect(UIManager.measureLayout).not.toBeCalled();
334
+
335
+ const successCallback = jest.fn();
336
+ const failureCallback = jest.fn();
337
+ viewRef.measureLayout(otherRef, successCallback, failureCallback);
338
+
339
+ expect(UIManager.measureLayout).toHaveBeenCalledTimes(1);
340
+ expect(UIManager.measureLayout).toHaveBeenCalledWith(
341
+ expect.any(Number),
342
+ expect.any(Number),
343
+ expect.any(Function),
344
+ expect.any(Function),
345
+ );
346
+
347
+ const args = UIManager.measureLayout.mock.calls[0];
348
+ expect(args[0]).not.toEqual(args[1]);
349
+ expect(successCallback).not.toBeCalled();
350
+ expect(failureCallback).not.toBeCalled();
351
+ args[2]('fail');
352
+ expect(failureCallback).toBeCalledWith('fail');
353
+
354
+ expect(successCallback).not.toBeCalled();
355
+ args[3]('success');
356
+ expect(successCallback).toBeCalledWith('success');
357
+ });
358
+ });
359
+
360
it('returns the correct instance and calls it in the callback', () => {
361
const View = createReactNativeComponentClass('RCTView', () => ({
362
validAttributes: {foo: true},
packages/react-native-renderer/src/__tests__/ReactNativeMount-test.internal.js
+127
@@ -249,6 +249,133 @@ describe('ReactNative', () => {
249
});
250
});
251
252
+ it('should support reactTag in ref.measureLayout', () => {
253
+ const View = createReactNativeComponentClass('RCTView', () => ({
254
+ validAttributes: {foo: true},
255
+ uiViewClassName: 'RCTView',
256
+ }));
257
+
258
+ class Subclass extends ReactNative.NativeComponent {
259
+ render() {
260
+ return <View>{this.props.children}</View>;
261
+ }
262
+ }
263
+
264
+ const CreateClass = createReactClass({
265
+ mixins: [NativeMethodsMixin],
266
+ render() {
267
+ return <View>{this.props.children}</View>;
268
+ },
269
+ });
270
+
271
+ [View, Subclass, CreateClass].forEach(Component => {
272
+ UIManager.measureLayout.mockReset();
273
+
274
+ let viewRef;
275
+ let otherRef;
276
+ ReactNative.render(
277
+ <Component>
278
+ <Component
279
+ foo="bar"
280
+ ref={ref => {
281
+ viewRef = ref;
282
+ }}
283
+ />
284
+ <Component
285
+ ref={ref => {
286
+ otherRef = ref;
287
+ }}
288
+ />
289
+ </Component>,
290
+ 11,
291
+ );
292
+
293
+ expect(UIManager.measureLayout).not.toBeCalled();
294
+
295
+ const successCallback = jest.fn();
296
+ const failureCallback = jest.fn();
297
+ viewRef.measureLayout(
298
+ ReactNative.findNodeHandle(otherRef),
299
+ successCallback,
300
+ failureCallback,
301
+ );
302
+
303
+ expect(UIManager.measureLayout).toHaveBeenCalledTimes(1);
304
+ expect(UIManager.measureLayout).toHaveBeenCalledWith(
305
+ expect.any(Number),
306
+ expect.any(Number),
307
+ expect.any(Function),
308
+ expect.any(Function),
309
+ );
310
+
311
+ const args = UIManager.measureLayout.mock.calls[0];
312
+ expect(args[0]).not.toEqual(args[1]);
313
+ expect(successCallback).not.toBeCalled();
314
+ expect(failureCallback).not.toBeCalled();
315
+ args[2]('fail');
316
+ expect(failureCallback).toBeCalledWith('fail');
317
+
318
+ expect(successCallback).not.toBeCalled();
319
+ args[3]('success');
320
+ expect(successCallback).toBeCalledWith('success');
321
+ });
322
+ });
323
+
324
+ it('should support ref in ref.measureLayout of host components', () => {
325
+ const View = createReactNativeComponentClass('RCTView', () => ({
326
+ validAttributes: {foo: true},
327
+ uiViewClassName: 'RCTView',
328
+ }));
329
+
330
+ [View].forEach(Component => {
331
+ UIManager.measureLayout.mockReset();
332
+
333
+ let viewRef;
334
+ let otherRef;
335
+ ReactNative.render(
336
+ <Component>
337
+ <Component
338
+ foo="bar"
339
+ ref={ref => {
340
+ viewRef = ref;
341
+ }}
342
+ />
343
+ <View
344
+ ref={ref => {
345
+ otherRef = ref;
346
+ }}
347
+ />
348
+ </Component>,
349
+ 11,
350
+ );
351
+
352
+ expect(UIManager.measureLayout).not.toBeCalled();
353
+
354
+ const successCallback = jest.fn();
355
+ const failureCallback = jest.fn();
356
+ viewRef.measureLayout(otherRef, successCallback, failureCallback);
357
+
358
+ expect(UIManager.measureLayout).toHaveBeenCalledTimes(1);
359
+ expect(UIManager.measureLayout).toHaveBeenCalledWith(
360
+ expect.any(Number),
361
+ expect.any(Number),
362
+ expect.any(Function),
363
+ expect.any(Function),
364
+ );
365
+
366
+ const args = UIManager.measureLayout.mock.calls[0];
367
+ expect(args[0]).not.toEqual(args[1]);
368
+ expect(successCallback).not.toBeCalled();
369
+ expect(failureCallback).not.toBeCalled();
370
+ args[2]('fail');
371
+ expect(failureCallback).toBeCalledWith('fail');
372
+
373
+ expect(successCallback).not.toBeCalled();
374
+ args[3]('success');
375
+ expect(successCallback).toBeCalledWith('success');
376
+ });
377
+ });
378
+
379
it('returns the correct instance and calls it in the callback', () => {
380
const View = createReactNativeComponentClass('RCTView', () => ({
381
validAttributes: {foo: true},