React useRef Explained: Values That Persist Without Re-rendering

useRef stores a mutable value that survives between renders without triggering a new render. Learn how it works and when to reach for it.

6 min read

useRef stores a value that survives between renders without causing a new render when it changes. It returns a plain JavaScript object with a single current property you can read or write freely. That makes it the right tool for values your component must remember but the UI never displays.

What useRef returns

Call useRef at the top level of your component and pass the starting value as the only argument. React creates the ref object once and returns that same object on every later render, so anything you store in it sticks around.

PartBehavior
initialValueThe starting value of current. Ignored after the first render.
currentA mutable property you can read and write at any time.
Return objectThe same object on every render of the component.

Unlike state, the object is mutable. Assigning a new value to current updates it immediately, and React does not re-render in response.

Why a normal variable is not enough

A component function runs from scratch on every render, so a local variable declared inside it resets every time. A timer ID stored that way is lost as soon as any re-render happens.

App.jsxApp.jsx
import { useRef } from "react";
 
function Chat() {
  const timeoutRef = useRef(null);
 
  function handleSend() {
    timeoutRef.current = setTimeout(() => console.log("Sent"), 2000);
  }
 
  function handleCancel() {
    clearTimeout(timeoutRef.current);
  }
}

The ref keeps the same object across renders, so the timer ID survives until it is cleared. The cancel handler can always find it, which a plain variable could not guarantee.

A ref does not trigger a re-render

The core tradeoff is that React never notices when current changes. That is the feature, but it also means a ref cannot drive the screen.

A counter built on a ref updates its number silently and never refreshes the label, because no re-render happens. A value used for rendering belongs in state. See useRef vs useState for the decision rule.

A stopwatch: state and a ref working together

A stopwatch needs two kinds of data. The elapsed time is shown on screen, so it is state. The interval ID is only used to stop the timer, so it is a ref.

App.jsxApp.jsx
import { useState, useRef } from "react";
 
function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);
  const intervalRef = useRef(null);
  function handleStart() {
    clearInterval(intervalRef.current);
    intervalRef.current = setInterval(() => setNow(Date.now()), 10);
    setStartTime(Date.now());
    setNow(Date.now());
  }

The two state variables drive the display, because React re-renders when they change. The ref only stores the timer ID, which never appears in the JSX and does not need a render.

App.jsxApp.jsx
  function handleStop() {
    clearInterval(intervalRef.current);
  }
  const secondsPassed =
    startTime !== null && now !== null ? (now - startTime) / 1000 : 0;
 
  return (
    <>
      <p>Time passed: {secondsPassed.toFixed(3)}</p>
      <button onClick={handleStart}>Start</button>
      <button onClick={handleStop}>Stop</button>
    </>
  );
}

The screen shows the elapsed time updating every 10 milliseconds. Storing the interval ID in a ref avoids an extra re-render when the timer is created or cleared.

When to use a ref

Reach for a ref when the component must step outside React and remember something the UI does not render.

  • Storing timer or interval IDs.
  • Holding DOM nodes, which the next articles cover in detail.
  • Caching values only event handlers or Effects read.

Rules that keep refs safe

  • Do not read or write ref.current during render, except for one-time lazy initialization.
  • Use state when the value must appear in the UI or influence the next render.
  • Treat refs as an escape hatch, not the main data flow.

What to learn next

Refs are most often used to touch DOM nodes React manages for you. Continue with focusing an input with useRef.

Rune AI

Rune AI

Key Insights

  • useRef returns an object with a current property that persists across renders.
  • Changing current does not trigger a re-render, unlike state.
  • Store values in a ref only when they are not used for rendering.
  • Read and write refs in event handlers or Effects, not during render.
  • Use state for anything the UI must display or react to.
RunePowered by Rune AI

Frequently Asked Questions

Does changing a ref trigger a re-render?

No. Changing ref.current does not notify React, so the component does not render again. Use state when the value must appear in the UI.

When does useRef return a new object?

Never during the lifetime of the component. React creates the ref object on the first render and returns the same object on every later render.

Can I read ref.current during render?

Avoid it. Reading or writing ref.current during render makes output unpredictable. Read and write refs from event handlers or Effects instead.

Conclusion

useRef is an escape hatch that keeps a mutable value alive across renders without forcing a re-render. Use it for values that only event handlers and Effects need, such as timer IDs and DOM nodes, and switch to state whenever the value must appear on screen.