Skip to content

Commit 381c5e1

Browse files
committed
Workaround for React Native incorrectly measuring objects on Android.
1 parent eb35703 commit 381c5e1

File tree

2 files changed

+124
-2
lines changed

2 files changed

+124
-2
lines changed

react-native/hooks/useMeasure/index.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,30 @@ export function useMeasure<T extends NativeMethods> (
1919
const element = React.useRef<null | T>(null)
2020
const queuedLayout = React.useRef(false)
2121

22+
const wrapped = (x: undefined | number, y: undefined | number, width: undefined | number, height: undefined | number, pageX: undefined | number, pageY: undefined | number): void => {
23+
// According to types/documentation, these are never undefined. In
24+
// practice, however, they have been observed to be undefined multiple
25+
// times.
26+
if (x !== undefined && y !== undefined && width !== undefined && height !== undefined && pageX !== undefined && pageY !== undefined) {
27+
onMeasure(x, y, width, height, pageX, pageY)
28+
}
29+
}
30+
2231
return [
2332
(_element) => {
2433
element.current = _element
2534

2635
if (queuedLayout.current) {
2736
queuedLayout.current = false
2837

29-
_element?.measure(onMeasure)
38+
_element?.measure(wrapped)
3039
}
3140
},
3241
() => {
3342
if (element.current === null) {
3443
queuedLayout.current = true
3544
} else {
36-
element.current.measure(onMeasure)
45+
element.current.measure(wrapped)
3746
}
3847
}
3948
]

react-native/hooks/useMeasure/unit.tsx

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,116 @@ test('executes the callback once when the layout is computed, the ref is given a
168168

169169
renderer.unmount()
170170
})
171+
172+
for (const discardedUpdateScenario of [
173+
{
174+
name: 'when x is undefined',
175+
x: undefined,
176+
y: 40,
177+
width: 640,
178+
height: 320,
179+
pageX: 18,
180+
pageY: 72
181+
},
182+
{
183+
name: 'when y is undefined',
184+
x: 20,
185+
y: undefined,
186+
width: 640,
187+
height: 320,
188+
pageX: 18,
189+
pageY: 72
190+
},
191+
{
192+
name: 'when width is undefined',
193+
x: 20,
194+
y: 40,
195+
width: undefined,
196+
height: 320,
197+
pageX: 18,
198+
pageY: 72
199+
},
200+
{
201+
name: 'when height is undefined',
202+
x: 20,
203+
y: 40,
204+
width: 640,
205+
height: undefined,
206+
pageX: 18,
207+
pageY: 72
208+
},
209+
{
210+
name: 'when pageX is undefined',
211+
x: 20,
212+
y: 40,
213+
width: 640,
214+
height: 320,
215+
pageX: undefined,
216+
pageY: 72
217+
},
218+
{
219+
name: 'when pageY is undefined',
220+
x: 20,
221+
y: 40,
222+
width: 640,
223+
height: 320,
224+
pageX: 18,
225+
pageY: undefined
226+
}
227+
]) {
228+
describe(discardedUpdateScenario.name, () => {
229+
test('executes the callback once when the ref is given, the layout is computed and measurement completes', () => {
230+
const onMeasure = jest.fn()
231+
let ref: React.RefCallback<View>
232+
const measure = jest.fn()
233+
let onLayout: (event: LayoutChangeEvent) => void
234+
const Component: React.FunctionComponent = () => {
235+
const [_ref, _onLayout] = useMeasure(onMeasure)
236+
ref = _ref
237+
onLayout = _onLayout
238+
return <View />
239+
}
240+
const renderer = TestRenderer.create(<Component />)
241+
242+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
243+
ref!({ measure } as unknown as View)
244+
245+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
246+
onLayout!({} as unknown as LayoutChangeEvent)
247+
248+
measure.mock.calls[0][0](discardedUpdateScenario.x, discardedUpdateScenario.y, discardedUpdateScenario.width, discardedUpdateScenario.height, discardedUpdateScenario.pageX, discardedUpdateScenario.pageY)
249+
250+
expect(onMeasure).not.toHaveBeenCalled()
251+
expect(measure).toHaveBeenCalledTimes(1)
252+
253+
renderer.unmount()
254+
})
255+
256+
test('executes the callback once when the layout is computed, the ref is given and measurement completes', () => {
257+
const onMeasure = jest.fn()
258+
let ref: React.RefCallback<View>
259+
const measure = jest.fn()
260+
let onLayout: (event: LayoutChangeEvent) => void
261+
const Component: React.FunctionComponent = () => {
262+
const [_ref, _onLayout] = useMeasure(onMeasure)
263+
ref = _ref
264+
onLayout = _onLayout
265+
return <View />
266+
}
267+
const renderer = TestRenderer.create(<Component />)
268+
269+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
270+
onLayout!({} as unknown as LayoutChangeEvent)
271+
272+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
273+
ref!({ measure } as unknown as View)
274+
275+
measure.mock.calls[0][0](discardedUpdateScenario.x, discardedUpdateScenario.y, discardedUpdateScenario.width, discardedUpdateScenario.height, discardedUpdateScenario.pageX, discardedUpdateScenario.pageY)
276+
277+
expect(onMeasure).not.toHaveBeenCalled()
278+
expect(measure).toHaveBeenCalledTimes(1)
279+
280+
renderer.unmount()
281+
})
282+
})
283+
}

0 commit comments

Comments
 (0)