skip to content

useRef and useImperativeHandle

You will learn the mutable box that survives renders without causing one, how refs attach to DOM nodes (including callback refs and their React 19 cleanup return), and how useImperativeHandle narrows what a parent may call on a child. Interviewers probe ref-vs-state because writing to .current and expecting a re-render is a classic mistake.

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In React, what does useRef(initialValue) return, and why does assigning to ref.current not re-render the component the way a useState setter does?

level: juniorimportance: must knowfreq 82%

answer

  1. a box, not a subscription
  2. identity stable across renders
  3. nothing watches a property write
  4. state repaints, refs do not
  5. initial argument applied only once

basics

~20 s

useRef returns a stable mutable object shaped { current: initialValue } that survives every render. Writing to .current mutates that object directly instead of going through React's update queue, so React is never notified and schedules no re-render.

solid answer

~50 s

`useRef(initialValue)` creates one object on the first render and hands back that **same object identity** on every later render, with a single mutable `current` property. A state setter is a message to React: it enqueues an update and marks the component for re-render. `ref.current = x` is just a property write on an ordinary JavaScript object — nothing subscribes to it, so React has no idea it happened and the screen keeps showing the old value until something else triggers a render. That is why refs are the right home for values the UI does not display: a timer id, the previous value of a prop, a DOM node, a mutable third-party instance. If a change must appear on screen it belongs in state; if a change must never by itself repaint, it belongs in a ref.

code

javascript · 17 lines
javascript
import { useRef, useState } from 'react';

export function ClickCounters() {
  const refCount = useRef(0);
  const [stateCount, setStateCount] = useState(0);

  function handleClick() {
    refCount.current += 1;
    setStateCount((n) => n + 1);
  }

  return (
    <button onClick={handleClick}>
      ref: {refCount.current} / state: {stateCount}
    </button>
  );
}

go deeper

for a junior

Be able to say plainly that useRef returns an object with a current property that persists across renders, and that changing current does not update the screen. Name one honest use: a timer id or a DOM node.

for a middle

Explain the mechanism, not the rule: React re-renders on state updates, parent renders, context and store notifications, and a property write is none of them. Also explain that the initial argument is applied only on the first render.

for a senior

Show judgment about which values deserve a ref in a real codebase — latches, timer handles, previous values, library instances — and be ready to call out the anti-pattern of demoting displayed state to a ref to shave renders.

for a principal

Frame it as an API-contract question: refs are an untracked mutable escape hatch, so every ref in a codebase is a value outside React's data flow. Be ready to say where you allow them and what conventions keep them from becoming ambient shared state.

## Two places to keep a value A React function component runs top to bottom on every render, so any plain `let` declared inside it is created and discarded with that call. A component therefore needs somewhere outside the call to keep values, and React offers exactly two such places. They differ in one respect only: whether changing the value tells React to render again. `useState` gives you a snapshot plus a setter. The value you read is fixed for the duration of that render, and the setter is a message to React — enqueue an update, re-run this component, paint the result. `useRef` gives you a box. `useRef(initialValue)` returns an object shaped `{ current: initialValue }`. React creates it during the first render, stores it in that component's hook slot, and on every later render returns the *same* object — `===` to the one you got before. Nothing about the object is magic: it is an ordinary mutable JavaScript object that React keeps alive on your behalf. ## Why the write is invisible to React `ref.current = 5` is a plain property assignment. There is no setter to intercept it, no proxy, no subscription. React does not observe object mutation anywhere in its model — a component re-renders because of a state update, a parent re-rendering, a context change, or an external-store notification. A ref write is none of those. React is not slow to notice it; React never notices it. The practical consequence is a classic bug. Render `{ref.current}` in JSX, mutate `ref.current` in a click handler, and the DOM keeps showing the old number forever. Then it appears to "work" the moment something unrelated re-renders the component, which produces the puzzling report: *the counter only updates when I type in the other field*. ```jsx function BrokenCounter() { const clicks = useRef(0); // clicking mutates the box, but nothing repaints the label return <button onClick={() => { clicks.current++; }}>clicked {clicks.current}x</button>; } ``` ## The argument is used once The value you pass is applied only on the first render. On every later render React ignores it — but JavaScript still evaluates the expression before calling the hook, so `useRef(new ExpensiveThing())` constructs and throws away an object on every single render. That waste is why the lazy-initialisation idiom (`if (ref.current === null) ref.current = ...`) exists. ## What actually belongs in a ref Good candidates share one trait: changing them should not repaint anything. - the id returned by `setInterval` or `setTimeout`, needed only so cleanup can clear it - a DOM node you want to call a method on - a mutable instance owned by a third-party library - the previous value of a prop, kept for comparison - a latch such as "this form has already been submitted" The corollary matters as much: moving displayed state into a ref to "avoid re-renders" is not an optimisation, it is a bug. You removed the re-render *and* the update. If the user must see it, it is state. ## useRef versus createRef `createRef()` allocates a brand-new ref object on every call. In a class constructor, which runs once per instance, that is correct. In a function component body, which runs on every render, it silently discards the previous render's reference — you get a fresh empty box each time. `useRef` exists precisely because function bodies re-run; it is the hook-slot-backed version of the same idea. ## Identity is stable, contents are not Because React returns the same object every time, the ref object itself is a stable value. You can close over it in effects and callbacks without listing it in a dependency array, and the hooks linter knows this. What is *not* stable is `ref.current` — it is mutable by design, and React never re-runs anything when it changes. Depending on the current value is a genuinely different problem from depending on the box. ## Typing note With the React 19 type definitions, `useRef` requires an argument, so a DOM ref is written `useRef<HTMLInputElement | null>(null)` rather than `useRef<HTMLInputElement>()`. The runtime behaviour is unchanged; only the types got stricter.

  • If a ref never triggers a re-render, how does a value stored in a ref ever reach the screen?
    Only by riding along with a render caused by something else — a state update, a parent re-render, a context change. That is exactly why refs are a poor home for displayed values: the display is then at the mercy of unrelated updates and looks stale or laggy.
  • Is the argument you pass to useRef re-evaluated on later renders?
    React ignores it after the first render, but JavaScript still evaluates the expression on every render before the call. So `useRef(new Foo())` builds a throwaway `Foo` each time. When construction is expensive, pass `null` and create the value lazily instead.
  • Why is createRef wrong inside a function component?
    `createRef()` returns a new object on every call, and a function component body runs on every render, so each render gets a fresh empty ref and last render's node is lost. It suits a class constructor, which runs once. `useRef` keeps one object in the component's hook slot.

saying these in an interview costs you the question

  • Says writing to ref.current re-renders the component
  • Stores displayed values in a ref to avoid re-renders
  • Thinks useRef's argument is re-applied on every render
  • Calls createRef inside a function component body
  • Believes refs can only ever hold DOM nodes

context

open as a page

What does useImperativeHandle(ref, createHandle, deps) do in a React component, and why expose a custom handle instead of the raw DOM node?

level: middleimportance: should knowfreq 40%

basics

~20 s

useImperativeHandle replaces what the parent's ref points at with the object your createHandle function returns. The parent then sees only the methods you chose to expose, instead of the underlying DOM node or anything else inside the child.

open as a page

In React 19, how does a parent attach a ref to the <input> inside your custom function component, and what did forwardRef have to do before React 19?

level: middleimportance: should knowfreq 55%

basics

~20 s

In React 19, ref is an ordinary prop: accept it alongside the other props and pass it to the inner <input>. Before React 19, function components dropped ref, so you had to wrap them in forwardRef((props, ref) => ...).

open as a page

In React, why is reading or writing ref.current during rendering unsafe, and what is the accepted exception for lazily creating an expensive ref value?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Rendering must be pure and repeatable: React may run a component twice, discard the result, or restart it. A ref is untracked mutable state, so render-phase reads can observe a value from a render nobody saw, and writes make the render impure. The one sanctioned exception is initialising a ref that is still null.

open as a page

In React 19, what may a callback ref return, and how does returning it change the way React detaches the ref?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

A React 19 callback ref may return a cleanup function. When it does, React runs that cleanup on detach instead of calling the callback a second time with null, giving refs the same attach/cleanup shape that effects already have.

open as a page