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
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.
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
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.
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
useRef | useState | |
|---|---|---|
| Changing it re-renders | No | Yes |
| Read during render? | Avoid | Yes |
| Value is mutable | Yes (ref.current = x) | No, replace via the setter |
| Use for | DOM 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:
<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:
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.currentduring render and expecting it to be set; it isnullon the first render. - Overusing refs to control the DOM directly (changing text, classes). Let React do that through state and JSX.
Key points
useRefreturns{ 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.