Kodokon kodokon.com

useRef and DOM manipulation

Manipulate the DOM and keep instance values with useRef, without breaking render purity.

7 min · 3 questions

Open this lesson in Kodokon

useRef returns a mutable box - an object with a single current property - whose identity survives every render. Two fundamental differences from state: modifying ref.current triggers no re-render, and the change is visible immediately, without waiting for the next render. Two families of use follow: referencing a DOM node, and storing an instance value that must survive renders without driving the display.

JSX
import { useEffect, useRef } from "react";

function SearchField() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} type="search" />;
}
React attaches the node to the ref after the render is committed.

On the instance-value side, anything that must persist without causing a render belongs in a ref: the id returned by setInterval, the instance of an external library (a map, an editor, a video player), or a value from a previous render. The golden rule remains the same: rendering must be a pure function of props and state. Refs are read and written in effects and event handlers, not during render.

JAVASCRIPT
import { useEffect, useRef } from "react";

function usePrevious(value) {
  const ref = useRef(undefined);

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}
The effect runs after the render: the ref stays one render behind.

To expose a DOM node from your own component, pass the ref along: since React 19, ref is an ordinary prop of function components; before that, going through forwardRef was mandatory. One last reflex to cultivate: prefer the declarative approach when state is enough to describe the result (a CSS class, a disabled attribute). Reserve refs for the territory React does not cover: focus, text selection, measuring an element, scrolling, media playback.

JSX
import { useRef } from "react";

function TextField({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

function Form() {
  const fieldRef = useRef(null);

  return (
    <>
      <TextField ref={fieldRef} />
      <button onClick={() => fieldRef.current.focus()}>
        Give focus
      </button>
    </>
  );
}
Since React 19, ref is passed like any other prop.

Knowledge check

Make sure you remember the key points of this lesson.

  1. What fundamental difference separates useRef from useState for storing a value?
    • Modifying ref.current triggers no re-render, unlike a state update
    • useRef keeps its value after the component unmounts
    • useRef can only store DOM nodes
    • Refs are shared between all instances of a component
  2. Why access inputRef.current in useEffect rather than during render?
    • Because useEffect batches DOM accesses for better performance
    • Because refs are locked for reading during render
    • Because React only attaches the node after commit: during the first render, current is still null
  3. What does usePrevious(value) return on the very first render?
    • The current value of value
    • undefined, because the effect that writes to the ref has not run yet
    • null
    • The value of the next render