If it cannot be avoided the suggested pattern extracted from the React docs would be:
import React, { Component } from 'react';
const Child = ({ setRef }) => <input type="text" ref={setRef} />;
class Parent extends Component {
constructor(props) {
super(props);
this.setRef = this.setRef.bind(this);
}
componentDidMount() {
// Calling a function on the Child DOM element
this.childRef.focus();
}
setRef(input) {
this.childRef = input;
}
render() {
return <Child setRef={this.setRef} />
}
}
The Parent forwards a function as prop bound to Parent'sthis. When React calls the Child'sref prop setRef it will assign the Child'sref to the Parent'schildRef property.
Recommended for React >= 16.3
Ref forwarding is an opt-in feature that lets some components take a ref they receive, and pass it further down (in other words, “forward” it) to a child.
We create Components that forward their ref with React.forwardRef.
The returned Component ref prop must be of the same type as the return type of React.createRef. Whenever React mounts the DOM node then property current of the ref created with React.createRef will point to the underlying DOM node.
Created Components are forwarding their ref to a child node.
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
// Assign the custom prop "forwardedRef" as a ref
return <Component ref={forwardedRef} {...rest} />;
}
}
// Note the second param "ref" provided by React.forwardRef.
// We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
// And it can then be attached to the Component.
return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
}
Using Ref forwarding you can pass the ref from parent to further down to a child.
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
Create a React ref by calling React.createRef and assign it to a ref variable.
Pass your ref down to by specifying it as a JSX attribute.
React passes the ref to the (props, ref) => ... function inside forwardRef as a second argument.
Forward this ref argument down to by specifying it as a JSX attribute.
When the ref is attached, ref.current will point to the DOM node.
Note
The second ref argument only exists when you define a component with React.forwardRef call. Regular functional or class components don’t receive the ref argument, and ref is not available in props either.
Ref forwarding is not limited to DOM components. You can forward refs to class component instances, too.
Here is how I solve the problem for dynamic components:
On the parent, dynamically create references to the child components, for example:
class Form extends Component {
fieldRefs: [];
// dynamically create the child references on mount/init
componentWillMount = () => {
this.fieldRefs = [];
for(let f of this.props.children) {
if (f && f.type.name == 'FormField') {
f.ref = createRef();
this.fieldRefs.push(f);
}
}
}
// used later to retrieve values of the dynamic children refs
public getFields = () => {
let data = {};
for(let r of this.fieldRefs) {
let f = r.ref.current;
data[f.props.id] = f.field.current.value;
}
return data;
}
}
The Child component (ie <FormField />) implements it's own 'field' ref, to be referred to from the parent:
class FormField extends Component {
field = createRef();
render() {
return(
<input ref={this.field} type={type} />
);
}
}
Then in your main page, the "parent's parent" component, you can get the field values from the reference with:
I implemented this because I wanted to encapsulate all generic form functionality from a main <Form /> component, and the only way to be able to have the main client/page component set and style its own inner components was to use child components (ie. <FormField /> items within the parent <Form />, which is inside some other <Page /> component).
So, while some might consider this a hack, it's just as hackey as React's attempts to block the actual 'ref' from any parent, which I think is a ridiculous design, however they want to rationalize it.