Skip to content

Commit 2e02469

Browse files
authored
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
1 parent 1b94fd2 commit 2e02469

File tree

5 files changed

+233
-4
lines changed

5 files changed

+233
-4
lines changed

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

+25-2
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,36 @@ class ReactFabricHostComponent {
119119
}
120120

121121
measureLayout(
122-
relativeToNativeNode: number,
122+
relativeToNativeNode: number | Object,
123123
onSuccess: MeasureLayoutOnSuccessCallback,
124124
onFail: () => void /* currently unused */,
125125
) {
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+
126149
UIManager.measureLayout(
127150
this._nativeTag,
128-
relativeToNativeNode,
151+
relativeNode,
129152
mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
130153
mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
131154
);

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

+25-2
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,36 @@ class ReactNativeFiberHostComponent {
7070
}
7171

7272
measureLayout(
73-
relativeToNativeNode: number,
73+
relativeToNativeNode: number | Object,
7474
onSuccess: MeasureLayoutOnSuccessCallback,
7575
onFail: () => void /* currently unused */,
7676
) {
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+
77100
UIManager.measureLayout(
78101
this._nativeTag,
79-
relativeToNativeNode,
102+
relativeNode,
80103
mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
81104
mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
82105
);

packages/react-native-renderer/src/__mocks__/UIManager.js

+1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ const RCTUIManager = {
153153
views.get(parentTag).children.forEach(tag => removeChild(parentTag, tag));
154154
}),
155155
replaceExistingNonRootView: jest.fn(),
156+
measureLayout: jest.fn(),
156157
__takeSnapshot: jest.fn(),
157158
};
158159

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

+55
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,61 @@ describe('ReactFabric', () => {
302302
});
303303
});
304304

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+
305360
it('returns the correct instance and calls it in the callback', () => {
306361
const View = createReactNativeComponentClass('RCTView', () => ({
307362
validAttributes: {foo: true},

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

+127
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,133 @@ describe('ReactNative', () => {
249249
});
250250
});
251251

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+
252379
it('returns the correct instance and calls it in the callback', () => {
253380
const View = createReactNativeComponentClass('RCTView', () => ({
254381
validAttributes: {foo: true},

0 commit comments

Comments
 (0)