Skip to content

Commit

Permalink
Workaround for React Native incorrectly measuring objects on Android.
Browse files Browse the repository at this point in the history
  • Loading branch information
jameswilddev committed Jul 26, 2024
1 parent eb35703 commit 381c5e1
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 2 deletions.
13 changes: 11 additions & 2 deletions react-native/hooks/useMeasure/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,30 @@ export function useMeasure<T extends NativeMethods> (
const element = React.useRef<null | T>(null)
const queuedLayout = React.useRef(false)

const wrapped = (x: undefined | number, y: undefined | number, width: undefined | number, height: undefined | number, pageX: undefined | number, pageY: undefined | number): void => {
// According to types/documentation, these are never undefined. In
// practice, however, they have been observed to be undefined multiple
// times.
if (x !== undefined && y !== undefined && width !== undefined && height !== undefined && pageX !== undefined && pageY !== undefined) {
onMeasure(x, y, width, height, pageX, pageY)
}
}

return [
(_element) => {
element.current = _element

if (queuedLayout.current) {
queuedLayout.current = false

_element?.measure(onMeasure)
_element?.measure(wrapped)
}
},
() => {
if (element.current === null) {
queuedLayout.current = true
} else {
element.current.measure(onMeasure)
element.current.measure(wrapped)
}
}
]
Expand Down
113 changes: 113 additions & 0 deletions react-native/hooks/useMeasure/unit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,116 @@ test('executes the callback once when the layout is computed, the ref is given a

renderer.unmount()
})

for (const discardedUpdateScenario of [
{
name: 'when x is undefined',
x: undefined,
y: 40,
width: 640,
height: 320,
pageX: 18,
pageY: 72
},
{
name: 'when y is undefined',
x: 20,
y: undefined,
width: 640,
height: 320,
pageX: 18,
pageY: 72
},
{
name: 'when width is undefined',
x: 20,
y: 40,
width: undefined,
height: 320,
pageX: 18,
pageY: 72
},
{
name: 'when height is undefined',
x: 20,
y: 40,
width: 640,
height: undefined,
pageX: 18,
pageY: 72
},
{
name: 'when pageX is undefined',
x: 20,
y: 40,
width: 640,
height: 320,
pageX: undefined,
pageY: 72
},
{
name: 'when pageY is undefined',
x: 20,
y: 40,
width: 640,
height: 320,
pageX: 18,
pageY: undefined
}
]) {
describe(discardedUpdateScenario.name, () => {
test('executes the callback once when the ref is given, the layout is computed and measurement completes', () => {
const onMeasure = jest.fn()
let ref: React.RefCallback<View>
const measure = jest.fn()
let onLayout: (event: LayoutChangeEvent) => void
const Component: React.FunctionComponent = () => {
const [_ref, _onLayout] = useMeasure(onMeasure)
ref = _ref
onLayout = _onLayout
return <View />
}
const renderer = TestRenderer.create(<Component />)

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
ref!({ measure } as unknown as View)

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
onLayout!({} as unknown as LayoutChangeEvent)

measure.mock.calls[0][0](discardedUpdateScenario.x, discardedUpdateScenario.y, discardedUpdateScenario.width, discardedUpdateScenario.height, discardedUpdateScenario.pageX, discardedUpdateScenario.pageY)

expect(onMeasure).not.toHaveBeenCalled()
expect(measure).toHaveBeenCalledTimes(1)

renderer.unmount()
})

test('executes the callback once when the layout is computed, the ref is given and measurement completes', () => {
const onMeasure = jest.fn()
let ref: React.RefCallback<View>
const measure = jest.fn()
let onLayout: (event: LayoutChangeEvent) => void
const Component: React.FunctionComponent = () => {
const [_ref, _onLayout] = useMeasure(onMeasure)
ref = _ref
onLayout = _onLayout
return <View />
}
const renderer = TestRenderer.create(<Component />)

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
onLayout!({} as unknown as LayoutChangeEvent)

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
ref!({ measure } as unknown as View)

measure.mock.calls[0][0](discardedUpdateScenario.x, discardedUpdateScenario.y, discardedUpdateScenario.width, discardedUpdateScenario.height, discardedUpdateScenario.pageX, discardedUpdateScenario.pageY)

expect(onMeasure).not.toHaveBeenCalled()
expect(measure).toHaveBeenCalledTimes(1)

renderer.unmount()
})
})
}

0 comments on commit 381c5e1

Please sign in to comment.