Skip to content

React Tutorial React Hooks

The useRef Hook: Refs and DOM Access

Lesson 65 of 156 2 min read By Deepak Paswan Updated Oct 10, 2026

Runnable lesson · press Try it on the examples to edit and run them here.

useRef gives you a box that survives re-renders and does not cause a re-render when you change it. It has two main uses: reaching a DOM element, and remembering a value that is not part of the screen.

The basics

App.jsx
JSX
const ref = useRef(initialValue);
// ref is an object: { current: initialValue }
ref.current;            // read
ref.current = 5;        // write: no re-render

React gives you the same object on every render.

Use 1: access a DOM element

Attach the ref to an element with the ref attribute. React sets ref.current to the real DOM node once it exists.

App.jsx
JSX
function App() {
  const inputRef = React.useRef(null);

  function handleClick() {
    inputRef.current.focus(); // call a DOM method
  }

  return (
    <div>
      <input ref={inputRef} aria-label="Name" placeholder="Name" />
      <button onClick={handleClick}>Focus the input</button>
    </div>
  );
}

Typical DOM jobs: focusing, scrolling an element into view, measuring size, playing or pausing video, selecting text. Use refs for these, not for changing content that React manages.

Focus when the component appears

App.jsx
JSX
function App() {
  const inputRef = React.useRef(null);

  React.useEffect(() => {
    inputRef.current.focus(); // the node exists after the first render
  }, []);

  return (
    <label>
      Search <input ref={inputRef} aria-label="Search" placeholder="Type here" />
    </label>
  );
}

(Note: automatically stealing focus can hurt accessibility. Do it for things like opening a dialog or search field the user asked for.)

Use 2: remember a value without rendering

A ref is perfect for values that matter to your logic but not to your screen: a timer id, the previous value, a flag, an animation frame id.

App.jsx
JSX
function App() {
  const [seconds, setSeconds] = React.useState(0);
  const timerId = React.useRef(null); // survives renders; changing it does not re-render

  function start() {
    if (timerId.current) return; // already running
    timerId.current = setInterval(() => setSeconds((s) => s + 1), 1000);
  }

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

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

If the timer id were state, every change would re-render for no reason.

Ref vs state

useRefuseState
Changing it re-rendersNoYes
Read during render?AvoidYes
Value is mutableYes (ref.current = x)No, replace via the setter
Use forDOM nodes, timer ids, “latest value”Anything the screen shows

Do not read or write ref.current during rendering (except to initialise it). React expects render to be pure. Read and write refs in event handlers and effects.

Callback refs and cleanup (React 19)

ref also accepts a function. In React 19 that function can return a cleanup function, called when the element is removed:

App.jsx
JSX
<div ref={(node) => {
  // node is the element
  const observer = new ResizeObserver(() => { /* ... */ });
  observer.observe(node);
  return () => observer.disconnect();   // cleanup (React 19)
}} />

Passing a ref to your own component

In React 19, ref is an ordinary prop for function components, so you can pass it down:

App.jsx
JSX
function TextInput({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

function App() {
  const inputRef = React.useRef(null);
  return (
    <div>
      <p>Custom input</p>
      <TextInput ref={inputRef} aria-label="Custom" />
      <button onClick={() => inputRef.current.focus()}>Focus custom input</button>
    </div>
  );
}

In React 18 and earlier you needed forwardRef for this. It still works, but it is no longer needed in React 19.

Common mistakes

  • Using a ref to hold data that the screen shows. The screen will not update. Use state.
  • Reading ref.current during render and expecting it to be set; it is null on the first render.
  • Overusing refs to control the DOM directly (changing text, classes). Let React do that through state and JSX.

Key points

  • useRef returns { current }, stable across renders; changing it never re-renders.
  • Use it for DOM nodes and for values not shown on screen.
  • Do not read or write refs during rendering.