mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Introduce 'setAndForwardRef' helper function (#21823)
Summary:
This PR introduces a new helper function called `setAndForwardRef`. It is intended to help with moving components that depend on `NativeMethodsMixin` off of `createReactClass`.
It allows for classes that depend on having a ref to a native component to be able to also forward the native component ref to user code.
Usage is like this:
```js
class MyView extends React.Component {
_nativeRef = null;
_setNativeRef = setAndForwardRef({
getForwardedRef: () => this.props.forwardedRef,
setLocalRef: ref => {
this._nativeRef = ref;
},
});
render() {
return <View ref={this._setNativeRef} />;
}
}
const MyViewWithRef = React.forwardRef((props, ref) => (
<MyView {...props} forwardedRef={ref} />
));
module.exports = MyViewWithRef;
```
Pull Request resolved: https://github.com/facebook/react-native/pull/21823
Differential Revision: D10436673
Pulled By: TheSavior
fbshipit-source-id: 32e167bb3ea3234f08d5715168b0e61e4e035a7c
This commit is contained in:
committed by
Facebook Github Bot
parent
0fab27cbac
commit
d6c8f189e7
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const React = require('React');
|
||||
const ReactTestRenderer = require('react-test-renderer');
|
||||
|
||||
const setAndForwardRef = require('setAndForwardRef');
|
||||
|
||||
describe('setAndForwardRef', () => {
|
||||
let innerFuncCalled = false;
|
||||
let outerFuncCalled = false;
|
||||
|
||||
class ForwardedComponent extends React.Component<{||}> {
|
||||
testFunc() {
|
||||
innerFuncCalled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
type Props = $ReadOnly<{|
|
||||
callFunc?: ?boolean,
|
||||
forwardedRef: React.Ref<typeof ForwardedComponent>,
|
||||
|}>;
|
||||
|
||||
class TestComponent extends React.Component<Props> {
|
||||
_nativeRef: ?React.ElementRef<typeof ForwardedComponent> = null;
|
||||
_setNativeRef = setAndForwardRef({
|
||||
getForwardedRef: () => this.props.forwardedRef,
|
||||
setLocalRef: ref => {
|
||||
this._nativeRef = ref;
|
||||
},
|
||||
});
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.callFunc) {
|
||||
outerFuncCalled = this._nativeRef && this._nativeRef.testFunc();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <ForwardedComponent ref={this._setNativeRef} />;
|
||||
}
|
||||
}
|
||||
|
||||
// $FlowFixMe - TODO T29156721 `React.forwardRef` is not defined in Flow, yet.
|
||||
const TestComponentWithRef = React.forwardRef((props, ref) => (
|
||||
<TestComponent {...props} forwardedRef={ref} />
|
||||
));
|
||||
|
||||
beforeEach(() => {
|
||||
innerFuncCalled = false;
|
||||
outerFuncCalled = false;
|
||||
});
|
||||
|
||||
it('should forward refs (function-based)', () => {
|
||||
let testRef: ?React.ElementRef<typeof ForwardedComponent> = null;
|
||||
|
||||
ReactTestRenderer.create(
|
||||
<TestComponentWithRef
|
||||
ref={ref => {
|
||||
testRef = ref;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const val = testRef && testRef.testFunc();
|
||||
|
||||
expect(innerFuncCalled).toBe(true);
|
||||
expect(val).toBe(true);
|
||||
});
|
||||
|
||||
it('should forward refs (createRef-based)', () => {
|
||||
const createdRef = React.createRef<typeof ForwardedComponent>();
|
||||
|
||||
ReactTestRenderer.create(<TestComponentWithRef ref={createdRef} />);
|
||||
|
||||
const val = createdRef.current && createdRef.current.testFunc();
|
||||
|
||||
expect(innerFuncCalled).toBe(true);
|
||||
expect(val).toBe(true);
|
||||
});
|
||||
|
||||
it('should forward refs (string-based)', () => {
|
||||
class Test extends React.Component<{||}> {
|
||||
refs: $ReadOnly<{|
|
||||
stringRef?: ?React.ElementRef<typeof ForwardedComponent>,
|
||||
|}>;
|
||||
|
||||
componentDidMount() {
|
||||
/* eslint-disable react/no-string-refs */
|
||||
this.refs.stringRef && this.refs.stringRef.testFunc();
|
||||
/* eslint-enable react/no-string-refs */
|
||||
}
|
||||
|
||||
render() {
|
||||
/**
|
||||
* Can't directly pass the test component to `ReactTestRenderer.create`,
|
||||
* otherwise it will throw. See:
|
||||
* https://reactjs.org/warnings/refs-must-have-owner.html#strings-refs-outside-the-render-method
|
||||
*/
|
||||
/* eslint-disable react/no-string-refs */
|
||||
return <TestComponentWithRef ref="stringRef" />;
|
||||
/* eslint-enable react/no-string-refs */
|
||||
}
|
||||
}
|
||||
|
||||
ReactTestRenderer.create(<Test />);
|
||||
|
||||
expect(innerFuncCalled).toBe(true);
|
||||
});
|
||||
|
||||
it('should be able to use the ref from inside of the forwarding class', () => {
|
||||
expect(() =>
|
||||
ReactTestRenderer.create(<TestComponentWithRef callFunc={true} />),
|
||||
).not.toThrow();
|
||||
|
||||
expect(innerFuncCalled).toBe(true);
|
||||
expect(outerFuncCalled).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type React from 'React';
|
||||
|
||||
type Args = $ReadOnly<{|
|
||||
getForwardedRef: () => ?React.Ref<any>,
|
||||
setLocalRef: (ref: React.ElementRef<any>) => mixed,
|
||||
|}>;
|
||||
|
||||
/**
|
||||
* This is a helper function for when a component needs to be able to forward a ref
|
||||
* to a child component, but still needs to have access to that component as part of
|
||||
* its implementation.
|
||||
*
|
||||
* Its main use case is in wrappers for native components.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* class MyView extends React.Component {
|
||||
* _nativeRef = null;
|
||||
*
|
||||
* _setNativeRef = setAndForwardRef({
|
||||
* getForwardedRef: () => this.props.forwardedRef,
|
||||
* setLocalRef: ref => {
|
||||
* this._nativeRef = ref;
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* render() {
|
||||
* return <View ref={this._setNativeRef} />;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* const MyViewWithRef = React.forwardRef((props, ref) => (
|
||||
* <MyView {...props} forwardedRef={ref} />
|
||||
* ));
|
||||
*
|
||||
* module.exports = MyViewWithRef;
|
||||
*/
|
||||
|
||||
function setAndForwardRef({getForwardedRef, setLocalRef}: Args) {
|
||||
return function forwardRef(ref: React.ElementRef<any>) {
|
||||
const forwardedRef = getForwardedRef();
|
||||
|
||||
setLocalRef(ref);
|
||||
|
||||
// Forward to user ref prop (if one has been specified)
|
||||
if (typeof forwardedRef === 'function') {
|
||||
// Handle function-based refs. String-based refs are handled as functions.
|
||||
forwardedRef(ref);
|
||||
} else if (typeof forwardedRef === 'object' && forwardedRef != null) {
|
||||
// Handle createRef-based refs
|
||||
forwardedRef.current = ref;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = setAndForwardRef;
|
||||
Reference in New Issue
Block a user