[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

View File

@@ -123,6 +123,19 @@ describe('components/Text', () => {
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', () => {
const ref = React.createRef();
act(() => {

View File

@@ -127,15 +127,15 @@ describe('components/View', () => {
expect(ref).toBeCalled();
});
test('is not called for props changes', () => {
test('is not called for prop changes', () => {
const ref = jest.fn();
let rerender;
act(() => {
({ rerender } = render(<View ref={ref} testID="123" />));
({ rerender } = render(<View nativeID="123" ref={ref} style={{ borderWidth: 5 }} />));
});
expect(ref).toHaveBeenCalledTimes(1);
act(() => {
rerender(<View ref={ref} testID="1234" />);
rerender(<View nativeID="1234" ref={ref} style={{ borderWidth: 6 }} />);
});
expect(ref).toHaveBeenCalledTimes(1);
});

View File

@@ -7,9 +7,15 @@
* @flow
*/
import type { GenericStyleProp } from '../../types';
import type { ViewProps } from '../../Exports/View';
import UIManager from '../../exports/UIManager';
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) {
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
* 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 { classList, style, pointerEvents } = props;
const setNativePropsArgsRef = useRef(null);
setNativePropsArgsRef.current = { classList, pointerEvents, style };
return useMemo(
() => (hostNode: any) => {
if (hostNode != null) {
hostNode.measure = callback => UIManager.measure(hostNode, callback);
hostNode.measureLayout = (relativeToNode, success, failure) =>
UIManager.measureLayout(hostNode, relativeToNode, failure, success);
hostNode.measureInWindow = callback => UIManager.measureInWindow(hostNode, callback);
hostNode.setNativeProps = nativeProps =>
setNativeProps(hostNode, nativeProps, classList, pointerEvents, style, previousStyleRef);
}
return hostNode;
},
[classList, pointerEvents, style]
);
// Avoid creating a new ref on every render. The props only need to be
// available to 'setNativeProps' when it is called.
const ref = useStable(() => (hostNode: any) => {
if (hostNode != null) {
hostNode.measure = callback => UIManager.measure(hostNode, callback);
hostNode.measureLayout = (relativeToNode, success, failure) =>
UIManager.measureLayout(hostNode, relativeToNode, failure, success);
hostNode.measureInWindow = callback => UIManager.measureInWindow(hostNode, callback);
hostNode.setNativeProps = nativeProps => {
const { classList, style, pointerEvents } = setNativePropsArgsRef.current || emptyObject;
setNativeProps(hostNode, nativeProps, classList, pointerEvents, style, previousStyleRef);
};
}
});
return ref;
}