Introduction
React.js makes it easier to create user interfaces, but it can be difficult to directly access the DOM and component instances. forwardRef
helps solve this problem by making it simpler to pass and manage references.
Direct Access Challenges in Component Libraries
Let's say we're developing a CustomInput
component to expose from our library, intending to let developers control focus or integrate with third-party libraries requiring direct DOM access. A common challenge arises when trying to allow the parent component to directly interact with the CustomInput
's underlying DOM node.
Consider the initial approach without forwardRef
:
// CustomInput component without forwardRef
function CustomInput(props) {
// Attempting direct DOM access
return <input {...props} />;
}
// Attempting to use ref in a parent component
function ParentComponent() {
const inputRef = React.createRef();
// Attempt to focus the input on button click fails
// because CustomInput doesn't forward the ref
const focusInput = () => {
inputRef.current.focus(); // Error: inputRef.current is null
};
return (
<>
<CustomInput ref={inputRef} />
<button onClick={focusInput}>Focus the input</button>
</>
);
}
In this scenario, the parent component's attempt to focus the CustomInput
fails because ref
is not automatically passed through. Older solutions, such as using props to pass down callbacks or using context, make things more complicated and hide the intended direct control features.
These challenges reveal React's initial refs limitations, emphasizing the need for forwardRef
to improve component interaction and library usability.
Introduction of forwardRef
forwardRef
lets refs be sent to a DOM node or component instance inside a component, making direct interactions easier and helping components work together better.
How forwardRef Works
forwardRef
wraps a component, enabling it to receive a ref
and forward it to a child DOM element or component. This is crucial for operations requiring direct access, like input focus or animation control.
Practical Example
Using forwardRef
simplifies focusing on a nested button component:
import React, { forwardRef } from 'react';
const FancyButton = forwardRef((props, ref) => (
<button ref={ref} {...props}>{props.children}</button>
));
// Parent component using FancyButton
const ParentComponent = () => {
const buttonRef = React.useRef(null);
const focusButton = () => buttonRef.current.focus();
return (
<>
<FancyButton ref={buttonRef}>Click Me</FancyButton>
<button onClick={focusButton}>Focus the Fancy Button</button>
</>
);
};
Appropriate Use Cases for forwardRef
forwardRef
is especially helpful for working with direct DOM access, managing focus, animations, and integrating with libraries focused on the DOM.
It often comes in handy for making reusable component libraries that need encapsulation and direct instance access.
However, be careful with overusing refs, as the React documentation says:
Do not overuse refs. You should only use refs for imperative behaviors that you can’t express as props: for example, scrolling to a node, focusing a node, triggering an animation, selecting text, and so on.
If you can express something as a prop, you should not use a ref. For example, instead of exposing an imperative handle like
{ open, close }
from aModal
component, it is better to takeisOpen
as a prop like<Modal isOpen={isOpen} />
.