useRef creates a plain mutable object { current: initialValue } that stays alive across every render of a component. Unlike useState, changing ref.current never triggers a component re-render. Developers use useRef for two jobs: reaching real browser DOM nodes (like focusing an input or scrolling a section) and storing non-visual values (like setInterval IDs or previous props).
Attach a ref to any JSX tag using the ref={inputRef} prop. After React mounts the DOM node, inputRef.current points directly to that HTML element.
import { useRef, useState } from 'react';
export function StopwatchInput() {
const inputRef = useRef(null);
const timerRef = useRef(null);
const [seconds, setSeconds] = useState(0);
function startTimer() {
if (timerRef.current !== null) return;
timerRef.current = setInterval(() => {
setSeconds((s) => s + 1);
}, 1000);
inputRef.current?.focus();
}
function stopTimer() {
clearInterval(timerRef.current);
timerRef.current = null;
}
return (
<div>
<input ref={inputRef} placeholder="Lap label..." />
<p>Elapsed: {seconds}s</p>
<button type="button" onClick={startTimer}>Start</button>
<button type="button" onClick={stopTimer}>Stop</button>
</div>
);
}
useRef vs useState at a Glance| Feature | useState |
useRef |
|---|---|---|
| Persists between renders | Yes | Yes |
| Triggers re-render on update | Yes | No |
| Update timing | Asynchronous (next render) | Synchronous (ref.current = ...) |
| Best suited for | UI data shown on screen | DOM nodes, timer IDs, previous values |
useRef Mistakesref.current During Render: Reading or mutating ref.current in the render body breaks component purity and causes unpredictable behavior in concurrent mode. Access ref.current only inside event handlers or useEffect.countRef.current += 1 will not update JSX text on screen. Use useState for anything visible in markup.forwardRef in React 19?No. React 19 allows custom function components to accept ref directly as a standard prop, simplifying reusable input and button wrappers.
inputRef.current null on the very first render pass?DOM nodes do not exist until React finishes rendering and commits them to the page, so refs become populated right before useEffect runs.
useRef take a callback instead of an object?Yes, JSX supports callback refs (ref={(node) => ...}), and React 19 supports returning a cleanup function directly from a callback ref.
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With