React useRef Hook

The React useRef hook gives you a box that holds a value for the whole life of a component. The value lives in ref.current. Unlike state, changing a ref does not trigger a re-render. Refs have two main uses: pointing to DOM elements and storing values that should not affect what is rendered.

Accessing a DOM Element

Pass the ref to an element's ref attribute. After rendering, ref.current is the real DOM node.

import { useRef } from 'react';

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

  return (
    <>
      <input ref={inputRef} placeholder="Search" />
      <button onClick={() => inputRef.current.focus()}>Focus search</button>
    </>
  );
}
(clicking the button moves the cursor into the input)

Storing a Value Without Re-rendering

A stopwatch needs to remember its interval id so it can stop later. The id never appears on screen, so a ref is perfect.

function Stopwatch() {
  const [seconds, setSeconds] = useState(0);
  const intervalRef = useRef(null);

  function start() {
    if (intervalRef.current) return;
    intervalRef.current = setInterval(() => setSeconds((s) => s + 1), 1000);
  }
  function stop() {
    clearInterval(intervalRef.current);
    intervalRef.current = null;
  }

  return (
    <>
      <p>{seconds}s</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </>
  );
}
0s -> 1s -> 2s ... (stops when Stop is clicked)

Counting Renders

If you used state here, updating it in the effect would cause another render and an endless loop. A ref changes silently.

function RenderCounter() {
  const [text, setText] = useState('');
  const renders = useRef(0);

  useEffect(() => {
    renders.current += 1;
  });

  return (
    <>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <p>Effect has run {renders.current} times</p>
    </>
  );
}
(type "abc")
Effect has run 3 times

useRef vs useState

  • useState: changing it re-renders; use it for anything shown on screen.
  • useRef: changing it does not re-render; use it for DOM nodes, timer ids and instance values.
  • Do not read or write ref.current during rendering; do it in event handlers and effects.

Passing Refs to Your Own Components

In React 18, a custom component cannot receive ref as a normal prop. Wrap it with forwardRef: const Input = forwardRef((props, ref) => <input ref={ref} {...props} />). (React 19 allows ref as a regular prop.)

Frequently asked questions

What is the difference between useRef and useState?

Both keep values between renders, but updating state re-renders the component while updating a ref does not. Use refs for values the UI does not display.

How do I focus an input in React?

Create a ref with useRef(null), attach it with ref={inputRef}, and call inputRef.current.focus() in an event handler or effect.

Why is my ref.current null?

The element has not rendered yet, or it is conditionally hidden. Access the ref in an effect or event handler, after React has attached it.