[fix] Avoid usePlatformMethods excess ref creation

Close #1777
This commit is contained in:
Charlie Croom
2020-10-20 11:53:45 -04:00
committed by Nicolas Gallagher
parent 2428b6c6fc
commit a2d72ee89c
3 changed files with 50 additions and 20 deletions
@@ -123,6 +123,19 @@ describe('components/Text', () => {
expect(ref).toBeCalled(); expect(ref).toBeCalled();
}); });
test('is not called for prop changes', () => {
const ref = jest.fn();
let rerender;
act(() => {
({ rerender } = render(<Text nativeID="123" ref={ref} style={{ borderWidth: 5 }} />));
});
expect(ref).toHaveBeenCalledTimes(1);
act(() => {
rerender(<Text nativeID="1234" ref={ref} style={{ borderWidth: 6 }} />);
});
expect(ref).toHaveBeenCalledTimes(1);
});
test('node has imperative methods', () => { test('node has imperative methods', () => {
const ref = React.createRef(); const ref = React.createRef();
act(() => { act(() => {
@@ -127,15 +127,15 @@ describe('components/View', () => {
expect(ref).toBeCalled(); expect(ref).toBeCalled();
}); });
test('is not called for props changes', () => { test('is not called for prop changes', () => {
const ref = jest.fn(); const ref = jest.fn();
let rerender; let rerender;
act(() => { act(() => {
({ rerender } = render(<View ref={ref} testID="123" />)); ({ rerender } = render(<View nativeID="123" ref={ref} style={{ borderWidth: 5 }} />));
}); });
expect(ref).toHaveBeenCalledTimes(1); expect(ref).toHaveBeenCalledTimes(1);
act(() => { act(() => {
rerender(<View ref={ref} testID="1234" />); rerender(<View nativeID="1234" ref={ref} style={{ borderWidth: 6 }} />);
}); });
expect(ref).toHaveBeenCalledTimes(1); expect(ref).toHaveBeenCalledTimes(1);
}); });
@@ -7,9 +7,15 @@
* @flow * @flow
*/ */
import type { GenericStyleProp } from '../../types';
import type { ViewProps } from '../../Exports/View';
import UIManager from '../../exports/UIManager'; import UIManager from '../../exports/UIManager';
import createDOMProps from '../createDOMProps'; import createDOMProps from '../createDOMProps';
import { useMemo, useRef } from 'react'; import useStable from '../useStable';
import { useRef } from 'react';
const emptyObject = {};
function setNativeProps(node, nativeProps, classList, pointerEvents, style, previousStyleRef) { function setNativeProps(node, nativeProps, classList, pointerEvents, style, previousStyleRef) {
if (node != null && nativeProps) { if (node != null && nativeProps) {
@@ -43,22 +49,33 @@ function setNativeProps(node, nativeProps, classList, pointerEvents, style, prev
* Adds non-standard methods to the hode element. This is temporarily until an * Adds non-standard methods to the hode element. This is temporarily until an
* API like `ReactNative.measure(hostRef, callback)` is added to React Native. * API like `ReactNative.measure(hostRef, callback)` is added to React Native.
*/ */
export default function usePlatformMethods(props: Object) { export default function usePlatformMethods({
classList,
pointerEvents,
style
}: {
classList?: Array<string | boolean>,
style?: GenericStyleProp<*>,
pointerEvents?: $PropertyType<ViewProps, 'pointerEvents'>
}) {
const previousStyleRef = useRef(null); const previousStyleRef = useRef(null);
const { classList, style, pointerEvents } = props; const setNativePropsArgsRef = useRef(null);
setNativePropsArgsRef.current = { classList, pointerEvents, style };
return useMemo( // Avoid creating a new ref on every render. The props only need to be
() => (hostNode: any) => { // available to 'setNativeProps' when it is called.
if (hostNode != null) { const ref = useStable(() => (hostNode: any) => {
hostNode.measure = callback => UIManager.measure(hostNode, callback); if (hostNode != null) {
hostNode.measureLayout = (relativeToNode, success, failure) => hostNode.measure = callback => UIManager.measure(hostNode, callback);
UIManager.measureLayout(hostNode, relativeToNode, failure, success); hostNode.measureLayout = (relativeToNode, success, failure) =>
hostNode.measureInWindow = callback => UIManager.measureInWindow(hostNode, callback); UIManager.measureLayout(hostNode, relativeToNode, failure, success);
hostNode.setNativeProps = nativeProps => hostNode.measureInWindow = callback => UIManager.measureInWindow(hostNode, callback);
setNativeProps(hostNode, nativeProps, classList, pointerEvents, style, previousStyleRef); hostNode.setNativeProps = nativeProps => {
} const { classList, style, pointerEvents } = setNativePropsArgsRef.current || emptyObject;
return hostNode; setNativeProps(hostNode, nativeProps, classList, pointerEvents, style, previousStyleRef);
}, };
[classList, pointerEvents, style] }
); });
return ref;
} }