What is a Ref?
A ref is a mutable value that persists across renders, similar to state — but unlike state, changing a ref does not trigger a re-render. useRef(initialValue) returns an object with a single .current property.
A Basic Ref
import { useRef } from "react";
function Example() {
const renderCount = useRef(0);
renderCount.current += 1;
return <p>This component has rendered {renderCount.current} times.</p>;
}Accessing DOM Elements
The most common use of a ref is to get direct access to a DOM node — for example, to call .focus() on an input, which isn’t something you can do declaratively through props alone.
Focusing an Input on Mount
import { useRef, useEffect } from "react";
function SearchInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} placeholder="Search..." />;
}Refs vs State
State is for values that affect what’s rendered on screen — changing state re-renders the component. Refs are for values you need to keep around between renders, but that shouldn’t trigger a re-render when they change.
State vs Refs
| State (useState) | Refs (useRef) | |
|---|---|---|
| Triggers a re-render on change | Yes | No |
| Value persists between renders | Yes | Yes |
| Typical use | Data shown in the UI | DOM access, timers, values that don’t affect rendering |
Storing a Timer ID in a Ref
function Stopwatch() {
const [seconds, setSeconds] = useState(0);
const intervalRef = useRef(null);
function start() {
intervalRef.current = setInterval(() => setSeconds((s) => s + 1), 1000);
}
function stop() {
clearInterval(intervalRef.current);
}
return (
<>
<p>{seconds}s</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</>
);
}Don’t Read or Write Refs During Rendering
Reading or writing ref.current while a component is rendering makes rendering unpredictable, since refs don’t follow React’s usual re-render rules. Only access refs inside event handlers or effects, not directly in the render body.
Best Practice
Reach for a ref only when you specifically need to escape React’s declarative model — direct DOM access, storing a mutable value like a timer ID, or measuring an element’s size. If a value affects what’s rendered, it belongs in state instead.