Skip to content

Commit a2d72ee

Browse files
comp615necolas
authored andcommitted
[fix] Avoid usePlatformMethods excess ref creation
Close #1777
1 parent 2428b6c commit a2d72ee

File tree

3 files changed

+50
-20
lines changed

3 files changed

+50
-20
lines changed

packages/react-native-web/src/exports/Text/__tests__/index-test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,19 @@ describe('components/Text', () => {
123123
expect(ref).toBeCalled();
124124
});
125125

126+
test('is not called for prop changes', () => {
127+
const ref = jest.fn();
128+
let rerender;
129+
act(() => {
130+
({ rerender } = render(<Text nativeID="123" ref={ref} style={{ borderWidth: 5 }} />));
131+
});
132+
expect(ref).toHaveBeenCalledTimes(1);
133+
act(() => {
134+
rerender(<Text nativeID="1234" ref={ref} style={{ borderWidth: 6 }} />);
135+
});
136+
expect(ref).toHaveBeenCalledTimes(1);
137+
});
138+
126139
test('node has imperative methods', () => {
127140
const ref = React.createRef();
128141
act(() => {

packages/react-native-web/src/exports/View/__tests__/index-test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,15 +127,15 @@ describe('components/View', () => {
127127
expect(ref).toBeCalled();
128128
});
129129

130-
test('is not called for props changes', () => {
130+
test('is not called for prop changes', () => {
131131
const ref = jest.fn();
132132
let rerender;
133133
act(() => {
134-
({ rerender } = render(<View ref={ref} testID="123" />));
134+
({ rerender } = render(<View nativeID="123" ref={ref} style={{ borderWidth: 5 }} />));
135135
});
136136
expect(ref).toHaveBeenCalledTimes(1);
137137
act(() => {
138-
rerender(<View ref={ref} testID="1234" />);
138+
rerender(<View nativeID="1234" ref={ref} style={{ borderWidth: 6 }} />);
139139
});
140140
expect(ref).toHaveBeenCalledTimes(1);
141141
});

packages/react-native-web/src/modules/usePlatformMethods/index.js

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@
77
* @flow
88
*/
99

10+
import type { GenericStyleProp } from '../../types';
11+
import type { ViewProps } from '../../Exports/View';
12+
1013
import UIManager from '../../exports/UIManager';
1114
import createDOMProps from '../createDOMProps';
12-
import { useMemo, useRef } from 'react';
15+
import useStable from '../useStable';
16+
import { useRef } from 'react';
17+
18+
const emptyObject = {};
1319

1420
function setNativeProps(node, nativeProps, classList, pointerEvents, style, previousStyleRef) {
1521
if (node != null && nativeProps) {
@@ -43,22 +49,33 @@ function setNativeProps(node, nativeProps, classList, pointerEvents, style, prev
4349
* Adds non-standard methods to the hode element. This is temporarily until an
4450
* API like `ReactNative.measure(hostRef, callback)` is added to React Native.
4551
*/
46-
export default function usePlatformMethods(props: Object) {
52+
export default function usePlatformMethods({
53+
classList,
54+
pointerEvents,
55+
style
56+
}: {
57+
classList?: Array<string | boolean>,
58+
style?: GenericStyleProp<*>,
59+
pointerEvents?: $PropertyType<ViewProps, 'pointerEvents'>
60+
}) {
4761
const previousStyleRef = useRef(null);
48-
const { classList, style, pointerEvents } = props;
62+
const setNativePropsArgsRef = useRef(null);
63+
setNativePropsArgsRef.current = { classList, pointerEvents, style };
4964

50-
return useMemo(
51-
() => (hostNode: any) => {
52-
if (hostNode != null) {
53-
hostNode.measure = callback => UIManager.measure(hostNode, callback);
54-
hostNode.measureLayout = (relativeToNode, success, failure) =>
55-
UIManager.measureLayout(hostNode, relativeToNode, failure, success);
56-
hostNode.measureInWindow = callback => UIManager.measureInWindow(hostNode, callback);
57-
hostNode.setNativeProps = nativeProps =>
58-
setNativeProps(hostNode, nativeProps, classList, pointerEvents, style, previousStyleRef);
59-
}
60-
return hostNode;
61-
},
62-
[classList, pointerEvents, style]
63-
);
65+
// Avoid creating a new ref on every render. The props only need to be
66+
// available to 'setNativeProps' when it is called.
67+
const ref = useStable(() => (hostNode: any) => {
68+
if (hostNode != null) {
69+
hostNode.measure = callback => UIManager.measure(hostNode, callback);
70+
hostNode.measureLayout = (relativeToNode, success, failure) =>
71+
UIManager.measureLayout(hostNode, relativeToNode, failure, success);
72+
hostNode.measureInWindow = callback => UIManager.measureInWindow(hostNode, callback);
73+
hostNode.setNativeProps = nativeProps => {
74+
const { classList, style, pointerEvents } = setNativePropsArgsRef.current || emptyObject;
75+
setNativeProps(hostNode, nativeProps, classList, pointerEvents, style, previousStyleRef);
76+
};
77+
}
78+
});
79+
80+
return ref;
6481
}

0 commit comments

Comments
 (0)