Menu

React useRef Hook: DOM Refs and Values That Persist

useRef gives a component a box, { current }, that keeps its contents between renders without causing a render when you change it. Use it to reach a DOM node (focus, scroll, measure) or to store a timer id or any value the screen does not show.

This page includes runnable editors - edit, run, and see output instantly.

useRef returns an object, { current: initialValue }, that React keeps for the lifetime of the component. Changing ref.current does not re-render anything. That makes refs right for two jobs: holding a DOM node so you can call methods like focus() on it, and remembering a value between renders that the screen does not need to show.

Passing the ref object to ref={inputRef} tells React to put the <input> DOM node in inputRef.current. Change focus() to select(), type something, and click again: the text gets highlighted.

The syntax

const myRef = useRef(initialValue);

myRef.current;          // read
myRef.current = value;  // write, no re-render
  • initialValue is used on the first render only. For DOM refs it is null.
  • React returns the same object on every render. Only current changes.
  • Reading and writing current is ordinary JavaScript. React does not track it.

A value that does not cause renders

State is for what the screen shows. A ref is for what the component needs to remember but not display. This block keeps one counter in a ref and one in state, and logs both:

Click Ref + 1 a few times. The console shows ref.current going up, but the page still says 0 and no render is logged. Now click State + 1: the component renders, and the ref counter on screen jumps to the number the ref already held. The ref kept its value the whole time; React just had no reason to draw it.

Refs vs state

useStateuseRef
Returns[value, setValue]{ current: value }
Changing itRenders the component againDoes nothing visible
When the new value is readableIn the next renderImmediately
Change it withThe setterAssignment, ref.current = x
Use forAnything the screen showsTimer ids, DOM nodes, previous values, flags

If you are unsure, ask whether the JSX reads the value. If it does, it belongs in state.

Storing a timer id

setInterval returns an id you need later to stop the timer. It is a good fit for a ref: the Stop handler needs it, but the screen never shows it, and keeping it in a plain variable would lose it on the next render.

Replace useRef(null) with let intervalRef = { current: null }; and try Start, wait two seconds, then Stop: the timer keeps going. Every tick renders the component, which creates a fresh object, so Stop clears nothing.

Remembering the previous value

An effect runs after the render, so it can compare the new value with the one stored in a ref, then save the new one for next time:

Click green, then blue: the console logs changed from red to green and changed from green to blue. Clicking the color that is already selected logs nothing, because the effect only runs when color changes.

Working with the DOM

Refs are how you call browser APIs that React has no prop for: focus(), select(), scrollIntoView(), play() on a video, or reading a size with getBoundingClientRect().

const lastItemRef = useRef(null);

function scrollToLast() {
    lastItemRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}

<li ref={lastItemRef}>Last item</li>

ref.current is null during the first render, because React has not created the DOM node yet, and again after the element is removed. Read it in event handlers and effects, where the node exists. To measure an element and position something before the browser paints, read the ref in useLayoutEffect.

Do not read or write ref.current during render

React expects the component body to be a pure calculation: same props and state, same JSX. A ref changes outside that model, so a render that reads or writes it can show different results for the same inputs, and React may call the body more often than you expect.

function Bad() {
    const count = useRef(0);
    count.current++;              // writing during render
    return <p>{count.current}</p>; // reading during render
}

Read and write refs in event handlers and effects. The one exception is lazy initialization, where you fill the ref once on the first render:

const playerRef = useRef(null);
if (playerRef.current === null) {
    playerRef.current = new VideoPlayer(); // runs once
}

(The ref counter demo above reads clicks.current in the JSX on purpose, to show that the screen does not follow it. Do not copy that into real code.)

Ref callbacks

Instead of a ref object, you can pass a function to ref. React calls it with the DOM node when the element is added. In React 19 the function can return a cleanup, which React calls when the element is removed. This is handy for a list, where you cannot call useRef once per item:

On load the console logs one attached line per item. Click Remove last and only detached Cheese appears. trackNode is defined outside the component, so it is the same function every render and React leaves the other items alone. Write it inline as ref={(node) => { ... }} instead and every click detaches and reattaches every item, because each render passes a new function.

Passing a ref to your own component

In React 19, ref is a regular prop for function components. The parent creates the ref, and the child puts it on the DOM element it wants to expose:

Before React 19 this needed forwardRef, which still works. The forwardRef page shows both versions and how to expose only a few methods with useImperativeHandle.

Common mistakes

Showing a ref value in the JSX. The screen does not follow it. If the user should see the value, use state.

Using ref.current as an effect dependency. [ref.current] reads the value during render and React does not know when it changes, so the effect does not re-run when it does. Use a ref callback to react to a node being attached.

Reading a DOM ref during the first render. It is null until React has created the node. Read it in an effect or a handler.

Creating a new object instead of using the hook. const ref = { current: null } in the component body is a fresh object on every render. Only useRef gives you the same one each time.

Frequently Asked Questions

What does useRef return?

A plain object with one property, current, set to the initial value you pass. React returns the same object on every render, so whatever you put in current is still there next time.

What is the difference between useRef and useState?

Changing state renders the component again; changing ref.current does not. Use state for values the screen shows, and a ref for values only your event handlers and effects need, like a timer id or a DOM node.

Why is ref.current null?

React sets ref.current to the DOM node after it creates the node, during the commit. During the first render it is still null, and it is null again after the element is removed. Read it in an event handler or an effect.

Does changing ref.current cause a re-render?

No. React does not know you changed it. The new value appears on screen only if something else, like a state update, renders the component again.

How do I pass a ref to a child component?

In React 19 a function component receives ref as a regular prop, so you pass <MyInput ref={inputRef} /> and the child puts it on its <input>. Older versions needed forwardRef.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED