Introduction
useRef returns a mutable object with a single .current property that persists for the full lifetime of the component. Unlike state, updating a ref's .current value does not trigger a re-render. This makes useRef useful for two distinct purposes: holding a reference to a DOM node (so you can call imperative APIs like focus() or measure dimensions), and storing any mutable value — a timer ID, a previous prop value, a counter — that needs to survive across renders but should not itself drive the UI.
Cricket analogy: A ref is like the twelfth man standing by the boundary rope holding a spare bat — always available and swappable without a public announcement (re-render), useful either for a physical prop (the DOM node) or for silently tracking something like the number of drinks breaks taken.
Syntax
import { useRef } from 'react';
function TextInputWithFocusButton() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>Focus the input</button>
</>
);
}Explanation
useRef(initialValue) returns an object shaped like { current: initialValue } that React keeps stable across every render — the same object identity is returned each time, only its .current property changes. When you pass a ref object to a JSX element's ref attribute, React sets ref.current to the underlying DOM node after mount and clears it to null on unmount, giving you direct imperative access. The second, equally important use case is as a generic 'instance variable': because reading or writing ref.current never schedules a re-render and its value is not reset between renders, it's the right tool for things like storing a setInterval ID to clear later, tracking whether a component is still mounted, or remembering the previous value of a prop for comparison. Contrast this with useState, where every update triggers a re-render and the value is tied to the rendered UI — refs are explicitly for values that live outside the render/UI cycle.
Cricket analogy: The same physical scorebook (stable object identity) is used all match, only the ink on the page changes; attaching it to the umpire's stand after the toss and removing it after the match mirrors ref.current being set on mount and cleared on unmount, useful for storing the drinks-break timer ID or the previous over's score for comparison, none of which needs a scoreboard announcement.
Example
function StopwatchLogger() {
const intervalRef = useRef(null);
const tickCountRef = useRef(0);
const [display, setDisplay] = useState(0);
function start() {
if (intervalRef.current !== null) return;
intervalRef.current = setInterval(() => {
tickCountRef.current += 1;
setDisplay(tickCountRef.current);
}, 1000);
}
function stop() {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
return (
<div>
<p>Ticks: {display}</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}Output
Clicking Start begins incrementing tickCountRef.current every second and calling setDisplay so the visible 'Ticks: N' text updates once per second. Notice that tickCountRef itself never causes a re-render when it changes — only the setDisplay call does. intervalRef.current holds the interval ID purely so Stop can clear it later; storing that ID in state instead would cause a pointless extra re-render every time it was set.
Cricket analogy: Clicking Start begins silently incrementing a private ball-counter (ref) every over while only the public scoreboard update (state) actually redraws the stadium screen once per over; the interval-ID ref just holds the id needed to stop the over-counter later, without itself triggering any screen redraw.
Key Takeaways
- useRef returns a stable object with a mutable .current property that persists across renders.
- Changing ref.current does NOT trigger a re-render, unlike useState.
- Attaching a ref to a JSX element's ref prop gives direct access to the underlying DOM node for imperative operations.
- Use refs for values that need to persist but shouldn't affect what is rendered, such as timer IDs or previous prop values.
- Don't read or write ref.current during rendering itself — treat it as an escape hatch used in event handlers and effects.
Practice what you learned
1. What happens when you update a ref's .current property?
2. Which of the following is a valid use case for useRef?
3. After attaching a ref to a DOM element via the ref attribute, when is ref.current typically set to the DOM node?
4. What is a key difference between useState and useRef for storing a value across renders?
Was this page helpful?
You May Also Like
The useState Hook
Master useState, the Hook that adds local state to function components, including initial values, updater functions, and functional updates.
The useEffect Hook
Understand useEffect for side effects in function components, covering the dependency array, cleanup functions, and effect timing.
Rules of Hooks
Learn the two Rules of Hooks — only call at the top level, only call from React functions — and why they exist.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics