forwardRef
forwardRef
lets your component expose a DOM node to parent component with a ref.
const SomeComponent = forwardRef(render)
Usage
Exposing a DOM node to the parent component
By default, each component’s DOM nodes are private. However, sometimes it’s useful to expose a DOM node to the parent—for example, to allow focusing it. To opt in, wrap your component definition into forwardRef()
:
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} />
</label>
);
});
You will receive a ref as the second argument after props. Pass it to the DOM node that you want to expose:
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} ref={ref} />
</label>
);
});
This lets the parent Form
component access the <input>
DOM node exposed by MyInput
:
function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
}
return (
<form>
<MyInput label="Enter your name:" ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}
This Form
component passes a ref to MyInput
. The MyInput
component forwards that ref to the <input>
browser tag. As a result, the Form
component can access that <input>
DOM node and call focus()
on it.
Keep in mind that by exposing a ref to the DOM node inside your component, you’re making it harder to change your component’s internals later. You will typically expose DOM nodes from reusable low-level components like buttons or text inputs, but you won’t do it for application-level components like an avatar or a comment.
Example 1 of 2: Focusing a text input
Clicking the button will focus the input. The Form
component defines a ref and passes it to the MyInput
component. The MyInput
component fowards that ref to the browser <input>
. This lets the Form
component focus the <input>
.
import { useRef } from 'react'; import MyInput from './MyInput.js'; export default function Form() { const ref = useRef(null); function handleClick() { ref.current.focus(); } return ( <form> <MyInput label="Enter your name:" ref={ref} /> <button type="button" onClick={handleClick}> Edit </button> </form> ); }
Forwarding a ref through multiple components
Instead of forwarding a ref
to a DOM node, you can forward it to your own component like MyInput
:
const FormField = forwardRef(function FormField(props, ref) {
// ...
return (
<>
<MyInput ref={ref} />
...
</>
);
});
If that MyInput
component forwards a ref to its <input>
, a ref to FormField
will give you that <input>
:
function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
}
return (
<form>
<FormField label="Enter your name:" ref={ref} isRequired={true} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}
The Form
component defines a ref and passes it to FormField
. The FormField
component forwards that ref to MyInput
, which forwards this ref to a browser <input>
DOM node. This is how Form
accesses that DOM node.
import { useRef } from 'react'; import FormField from './FormField.js'; export default function Form() { const ref = useRef(null); function handleClick() { ref.current.focus(); } return ( <form> <FormField label="Enter your name:" ref={ref} isRequired={true} /> <button type="button" onClick={handleClick}> Edit </button> </form> ); }
Exposing an imperative handle instead of a DOM node
Instead of exposing an entire DOM node, you can expose a custom object, called an imperative handle, with a more constrained set of methods. To do this, you’d need to define a separate ref to hold the DOM node:
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
// ...
return <input {...props} ref={inputRef} />;
});
Then pass the ref
you received to useImperativeHandle
and specify the value you want to expose to the ref
:
import { forwardRef, useRef, useImperativeHandle } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);
return <input {...props} ref={inputRef} />;
});
If some component gets a ref to MyInput
now, it will only receive your { focus, scrollIntoView }
object instead of the DOM node. This lets you limit the information you expose about your DOM node to the minimum.
import { useRef } from 'react'; import MyInput from './MyInput.js'; export default function Form() { const ref = useRef(null); function handleClick() { ref.current.focus(); // This won't work because the DOM node isn't exposed: // ref.current.style.opacity = 0.5; } return ( <form> <MyInput label="Enter your name:" ref={ref} /> <button type="button" onClick={handleClick}> Edit </button> </form> ); }
The methods you expose via an imperative handle don’t have to match the DOM methods exactly. For example, the Post
component in the example below exposes a scrollAndFocusAddComment
method via an imperative handle. This lets the parent Page
scroll the list of comments and focus the input field when you click the button:
import { useRef } from 'react'; import Post from './Post.js'; export default function Page() { const postRef = useRef(null); function handleClick() { postRef.current.scrollAndFocusAddComment(); } return ( <> <button onClick={handleClick}> Write a comment </button> <Post ref={postRef} /> </> ); }
Read more about using imperative handles.
Reference
forwardRef(render)
Call forwardRef()
to let your component receive a ref and forward it to a child component:
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
// ...
});
Parameters
render
: The render function for your component. React calls this function with the props andref
that your component received from its parent. The JSX you return will be the output of your component.
Returns
forwardRef
returns a React component that you can render in JSX. Unlike React components defined as plain functions, a component returned by forwardRef
is also able to receive a ref
prop.
Caveats
- In Strict Mode, React will call your render function twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. If your render function is pure (as it should be), this should not affect the logic of your component. The result from one of the calls will be ignored.
render
function
forwardRef
accepts a render function as an argument. React calls this function with props
and ref
:
const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});
Parameters
props
: The props passed by the parent component.ref
: Theref
attribute passed by the parent component. Theref
can be an object or a function. If the parent component has not passed a ref, it will benull
. You should either pass theref
you receive to another component, or pass it touseImperativeHandle
.
Returns
forwardRef
returns a React component that you can render in JSX. Unlike React components defined as plain functions, the component returned by forwardRef
is able to take a ref
prop.
Troubleshooting
My component is wrapped in forwardRef
, but the ref
to it is always null
This usually means that you forgot to actually use the ref
that you received.
For example, this component doesn’t do anything with its ref
:
const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input />
</label>
);
});
To fix it, pass the ref
down to a DOM node or another component that can accept a ref:
const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input ref={ref} />
</label>
);
});
The ref
to MyInput
could also be null
if some of the logic is conditional:
const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
{showInput && <input ref={ref} />}
</label>
);
});
If showInput
is false
, then the ref won’t be forwarded to any node, and a ref to MyInput
will remain empty. This is particularly easy to miss if the condition is hidden inside another component, like Panel
in this example:
const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
<Panel isExpanded={showInput}>
<input ref={ref} />
</Panel>
</label>
);
});