React useRef

useRef creates a mutable box that persists for the lifetime of a component without triggering a re-render when its value changes.

Refs vs State

useRef(initialValue) returns a plain object with a single mutable property, current, set to initialValue. Reassigning ref.current does not schedule a re-render and does not affect what's on screen — it simply persists across renders, making refs the right tool for values a component needs to remember but never needs to display.

Accessing DOM Nodes

The most common use of useRef is grabbing a reference to an actual DOM element. Pass the ref object to a JSX element's ref attribute, and React sets ref.current to that underlying DOM node right after it mounts — letting you call imperative DOM methods like focus() or measure its size.

  • Focusing an input programmatically
  • Measuring an element's size or position
  • Storing a timer or interval id so it can be cleared later
  • Remembering a previous prop or state value across renders
  • Storing any mutable value, like a render count, that must not trigger a re-render

Focusing an input on mount

import { useRef, useEffect } from "react";

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

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

  return <input ref={inputRef} placeholder="Search..." />;
}

export default SearchBox;

Refs are just as useful for values that have nothing to do with the DOM. A timer id, a WebSocket instance, or a previous value you want to compare against — anything mutable that should survive re-renders without itself causing one — belongs in a ref rather than in state.

Storing an interval id in a ref

import { useRef, useState } from "react";

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

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

  function stop() {
    clearInterval(intervalRef.current);
    intervalRef.current = null;
  }

  return (
    <div>
      <p>{seconds}s</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </div>
  );
}

export default Timer;
Note: Reach for useRef only when a value truly does not belong in the rendered output — for anything the user sees, useState is the correct tool because it schedules the re-render you actually need.

Tracking the previous value of a state variable

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

function PreviousValue() {
  const [count, setCount] = useState(0);
  const previousCountRef = useRef();

  useEffect(() => {
    previousCountRef.current = count;
  }, [count]);

  return (
    <div>
      <p>
        Now: {count}, before: {previousCountRef.current}
      </p>
      <button onClick={() => setCount((prev) => prev + 1)}>+</button>
    </div>
  );
}

export default PreviousValue;
AspectuseStateuseRef
Triggers re-render on changeYesNo
Value available during renderAlways current for that renderMay lag by one render if it's updated inside an effect
Typical useValues shown in the UIDOM nodes, timer ids, mutable values that don't affect output
Note: Never read or write ref.current during rendering itself (outside effects and event handlers) — refs sit outside React's rendering data flow, so doing so can produce inconsistent output between renders.

Exercise: React useRef

What's the key difference between updating a ref's .current and calling a useState setter?