forwardRef & useImperativeHandle

Reviewed & published by Brayan K

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.

Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

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.

function Form() {
  const inputRef = useRef(null);
  function handleClick() {
    inputRef.current.focus();        // command the DOM node
  }
  return (
    <>
      <input ref={inputRef} />         
      <button onClick={handleClick}>Focus</button>
    </>
  );
}
// A ref is a box with a .current property React fills in for you.
// For a DOM node, ref.current becomes that element so you can call
// imperative methods on it (focus, scrollTo, play...).
// We mock a DOM node so you can see the shape:

const inputRef = { current: null };

const fakeInputNode = {
  value: "",
  focus() { console.log("input focused!"); },
  select() { console.log("text selected!"); },
};

inputRef.current = fakeInputNode;   // React assigns the node here

inputRef.current.focus();           // input focused!
inputRef.current.select();          // text selected!
console.log("has node:", inputRef.current !== null);  // true

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 <TextInput ref={myRef} /> gives the parent the underlying <input>.

const TextInput = forwardRef(function TextInput(props, ref) {
  return <input ref={ref} placeholder={props.label} />;
});

// Parent can now focus the inner input:
function Parent() {
  const ref = useRef(null);
  return <TextInput ref={ref} label="Email" />;
}
// forwardRef passes the parent's ref DOWN to a chosen inner node.
// We model it: the component receives (props, ref) and attaches the
// ref to the node it wants the parent to control.

function forwardRef(render) {
  return function (props, ref) { return render(props, ref); };
}

const TextInput = forwardRef(function (props, ref) {
  const node = { focus() { console.log("focused", props.label); } };
  ref.current = node;             // attach ref to the inner node
  return "<input> for " + props.label;
});

const myRef = { current: null };
TextInput({ label: "Email" }, myRef);
myRef.current.focus();            // focused Email

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.

const FancyInput = forwardRef(function FancyInput(props, ref) {
  const innerRef = useRef(null);
  useImperativeHandle(ref, () => ({
    focus() { innerRef.current.focus(); },
    clear() { innerRef.current.value = ""; },
  }));
  return <input ref={innerRef} />;
});
// Parent: ref.current.focus();  ref.current.clear();
// 🎯 YOUR TURN — fill in each ___ then press "Try it Yourself".
// useImperativeHandle exposes a CUSTOM api on the parent's ref.
// Expose focus() and clear(); hide everything else.

const ref = { current: null };
let value = "hello";

function useImperativeHandle(targetRef, createHandle) {
  targetRef.current = createHandle();    // attach the custom api
}

useImperativeHandle(ref, () => ({
  focus() { console.log("focused"); },
  clear() { value = ___; console.log("cleared:", value); },  // 👉 ""
}));

ref.current.focus();                     // focused
ref.current.clear();                     // cleared:
console.log("can call scrollTo?", typeof ref.current.scrollTo);  // undefined

// ✅ Expected output:
//    focused
//    cleared:
//    undefined

🧩 Reorder Challenge

These lines build a forwarded input that exposes a custom API. Put them in the right order:

A.   useImperativeHandle(ref, () => ({
B.   const FancyInput = forwardRef(function (props, ref) {
C.   const innerRef = useRef(null);
D.   focus() { innerRef.current.focus(); } }));
E.   return <input ref={innerRef} />;

Common Errors (and the fix)

📋 Quick Reference

GoalTool
Command a DOM noderef.current.focus()
Send ref to a child nodeforwardRef((props, ref) => ...)
Expose a custom APIuseImperativeHandle(ref, () => ({...}))
React 19 shortcutfunction Input({ ref }) {...}

🧠 Quick Recall

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.

2. What does useImperativeHandle control?

Exactly which methods ref.current exposes, instead of the raw DOM node — a small, intentional API.

3. When is a ref the right call over a prop?

For imperative actions like focus, scroll, select, play, or measuring the DOM — things props can't naturally express.

Mini-Challenge: Expose an Imperative API

Build the handle a component would attach to a forwarded ref — just focus and scrollToTop — then call both. Run it and check your output.

// 🎯 MINI-CHALLENGE: expose a small imperative API
//
// Build createHandle() that returns an object with:
//   - focus()       -> logs "focus"
//   - scrollToTop() -> logs "scroll 0"
// Attach it to ref.current, then call BOTH methods.
//
// ✅ Expected output:
//    focus
//    scroll 0

const ref = { current: null };

// your code here

🎉 Lesson Complete

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.

Continue this course

Frequently asked questions

Why doesn't passing a ref to my custom component work by default?

ref is a special prop React handles itself, so it isn't forwarded to your inner DOM node automatically. In React 18 and earlier you wrap the component in forwardRef so the ref reaches the element you choose, like the underlying <input>.

What does useImperativeHandle do?

It lets a component expose a custom set of methods to its parent's ref instead of the raw DOM node. The parent calls ref.current.focus() or ref.current.scrollToTop(), and you decide exactly what those methods do — a small, deliberate imperative API.

When should I use a ref instead of props?

Props are for data and declarative behavior. Reach for a ref only for imperative actions that don't fit state: focusing an input, selecting text, scrolling, playing media, or measuring the DOM. If a prop can express it, prefer the prop.

Did React 19 change forwardRef?

Yes. In React 19 a function component can receive ref as a normal prop, so many cases no longer need forwardRef. useImperativeHandle still exists for exposing a custom API. forwardRef remains valid and is everywhere in existing code, so it's important to recognize.