forwardRef & useImperativeHandle
forwardRef is a React technique that lets a parent pass a ref through a custom component down to a real DOM node inside it, and useImperativeHandle lets that component expose a small set of imperative methods — like focus() or scrollTo() — instead of the raw node. Together they give a parent controlled, imperative access to a child.
Learn forwardRef & useImperativeHandle in our free React course — an interactive lesson with runnable examples, a practice exercise and a quick recall.
Part of the free React course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
1️⃣ Refs Hold a Node You Can Command
A ref created with useRef(null) is just an object with a mutable .current . Attach it to a DOM element and React fills .current with that node, letting you call imperative methods like focus() . The catch: pass that ref to your own component and it doesn't reach the inner node automatically.
2️⃣ forwardRef — Send the Ref Down
Wrap your component in forwardRef so it receives a second argument, ref , after props. Attach that ref to the inner element you want the parent to control. Now {' '} gives the parent the underlying .
3️⃣ useImperativeHandle — Expose a Small API
Often you don't want to hand the parent the raw node — you want a deliberate, minimal API. Inside the forwarded component, call useImperativeHandle(ref, () => ({' '})) to define exactly which methods ref.current exposes. The parent sees only focus() and clear() , not the whole DOM node.
These lines build a forwarded input that exposes a custom API. Put them in the right order:
📋 Quick Reference
1. Why doesn't a ref reach your custom component's input by default?
ref is special and isn't forwarded like other props. Wrap with forwardRef (React 18-) so it reaches the inner node.
Exactly which methods ref.current exposes, instead of the raw DOM node — a small, intentional API.
For imperative actions like focus, scroll, select, play, or measuring the DOM — things props can't naturally express.
Build the handle a component would attach to a forwarded ref — just focus and scrollToTop — then call both. Run it and check your output.
Practice quiz
What does a ref's .current hold after you attach it to a DOM element?
- The element's value as a string
- A copy of the component
- That DOM node, so you can call imperative methods on it
- null forever
Answer: That DOM node, so you can call imperative methods on it. React fills .current with the DOM node, letting you call methods like focus() on it.
Why doesn't passing a ref to your own component reach its inner node by default (React 18)?
- ref is a special prop React handles itself, so it isn't forwarded like other props
- Refs are disabled in custom components
- The node has no ref slot
- You must use useState instead
Answer: ref is a special prop React handles itself, so it isn't forwarded like other props. ref is special and not passed through like ordinary props; you wrap with forwardRef to route it.
What does forwardRef let a component do?
- Skip rendering
- Share state with its parent
- Memoize its output
- Receive a ref as a second argument and attach it to a chosen inner element
Answer: Receive a ref as a second argument and attach it to a chosen inner element. forwardRef gives the component (props, ref); attach ref to the inner node the parent should control.
What does useImperativeHandle do?
- Forwards all props automatically
- Lets the component expose a custom set of methods on the parent's ref instead of the raw node
- Creates a new ref
- Replaces useState
Answer: Lets the component expose a custom set of methods on the parent's ref instead of the raw node. It defines exactly which methods ref.current exposes (e.g. focus, clear), hiding the raw DOM node.
When is a ref the right choice over a prop?
- For imperative actions like focus, scroll, select, play, or measuring the DOM
- For passing data down
- For conditional rendering
- For styling
Answer: For imperative actions like focus, scroll, select, play, or measuring the DOM. Refs suit imperative actions props can't express. If a prop can express it, prefer the prop.
How did React 19 change forwardRef usage?
- It removed refs entirely
- It made forwardRef mandatory
- A function component can receive ref as a normal prop, so many cases no longer need forwardRef
- It renamed it to passRef
Answer: A function component can receive ref as a normal prop, so many cases no longer need forwardRef. In React 19, ref can be a normal prop on function components; useImperativeHandle still exists.
When should you read ref.current?
- During the render body
- Inside effects or event handlers, after it's set
- Before the component mounts
- In module scope
Answer: Inside effects or event handlers, after it's set. ref.current isn't set during render; read it in effects or event handlers where the node exists.
What's the benefit of exposing a small API with useImperativeHandle rather than the raw node?
- It is faster
- It removes the need for forwardRef
- It avoids re-renders
- Parents can only call the intended methods, not poke at internals
Answer: Parents can only call the intended methods, not poke at internals. A deliberate, minimal API (e.g. just focus/clear) keeps parents from reaching into internal DOM details.
Inside a forwardRef component, where do you usually attach a useRef for the real element?
- To the forwarded ref directly
- To an inner ref, then expose methods via useImperativeHandle on the forwarded ref
- To the parent's state
- You don't need an inner ref
Answer: To an inner ref, then expose methods via useImperativeHandle on the forwarded ref. Keep an inner ref on the real <input>, and expose only chosen methods through the forwarded ref.
Your custom component's ref.current is null. What is the common cause in React 18?
- You forgot useState
- The ref was created with useMemo
- You passed a ref to a custom component without wrapping it in forwardRef
- You called focus() too early
Answer: You passed a ref to a custom component without wrapping it in forwardRef. Without forwardRef, the ref never reaches a real node, so .current stays null.