Passing Refs (forwardRef)
Overview
You can easily attach a ref to standard HTML elements like <input ref={myRef} />. But what happens if you build a custom React Component, like <MyCustomInput />, and the parent component wants to attach a ref to it?
By default, React Components do not accept refs. If you try to write <MyCustomInput ref={myRef} />, React will completely ignore it and throw a warning in the console. This is an intentional security measure; React doesn't want parent components blindly reaching into child components and manipulating their DOM nodes, which violates the principle of encapsulation.
However, if you specifically want to allow a parent to access a child's DOM node (for example, a parent form needs to focus a specific child input if validation fails), the child component must explicitly consent to this by wrapping itself in the `forwardRef` function.
Syntax
import { forwardRef, useRef } from 'react';
// 1. The Child component explicitly opts-in to receiving a ref
// forwardRef wraps the component and provides a SECOND argument: 'ref'
const FancyInput = forwardRef((props, ref) => {
return (
<div className="fancy-wrapper">
{/* We take the ref provided by the parent, and attach it to our actual DOM node */}
<input ref={ref} className="fancy-text-field" placeholder={props.placeholder} />
</div>
);
});
// 2. The Parent component
function App() {
const inputRef = useRef(null);
return (
<form>
{/* Now the parent can successfully pass a ref to the custom component */}
<FancyInput ref={inputRef} placeholder="Enter name" />
<button type="button" onClick={() => inputRef.current.focus()}>
Focus the Input
</button>
</form>
);
}Common Pitfalls
- Forgetting the second parameter: Standard functional components only take one parameter:
(props). When you wrap a component inforwardRef, it changes the signature to take two parameters:(props, ref). A very common mistake is destructuring props but forgetting to grab the second ref parameter.
Interview Questions
Encapsulation. A component should be a black box that controls its own behavior. If a parent could arbitrarily grab a child's DOM nodes and manipulate them, it would make the child highly unstable and difficult to maintain. forwardRef forces the child to explicitly decide which DOM node it exposes to the parent.
useImperativeHandle and how does it relate to forwardRef?useImperativeHandle is an advanced hook used alongside forwardRef. Instead of exposing a raw DOM node to the parent, it allows the child to expose a highly restricted, custom object. For example, instead of giving the parent the whole <input> element, you only expose a { focus: () => ... } function, preventing the parent from doing anything dangerous.
Real-World Example
Design System Button Component: If you look at the source code for Material UI, Chakra UI, or Radix, literally every single interactive component uses forwardRef. It is an absolute requirement for building professional, flexible component libraries.
import { forwardRef } from 'react';
// In an enterprise design system, EVERY base component (Button, Input, Checkbox)
// is wrapped in forwardRef. This ensures that product engineers using the library
// can always access the underlying DOM node if they have a complex edge case.
export const Button = forwardRef(({ variant, children, ...rest }, ref) => {
return (
<button
ref={ref}
className={`btn ${variant === 'primary' ? 'bg-blue' : 'bg-gray'}`}
{...rest}
>
{children}
</button>
);
});
// Setting a display name is highly recommended when using forwardRef
// otherwise the component shows up as "Anonymous" in React DevTools
Button.displayName = 'Button';Check Your Knowledge
Test your understanding of Passing Refs (forwardRef) with these quick questions.