Navigate
Search topics, hooks, and fundamentals
The silent lens that remembers every setting without firing the shutter.
π· Theme: Photography Studio
Your component is a photography studio. useState is a loud shutter β every shot echoes through the gallery (triggers a re-render). useRef is the photographer's private notebook: it silently records shot counts, timer IDs, and exposure settings without disturbing the gallery. It can also point the lens at any DOM element.
useState remembers values β but every change fires the shutter and disturbs the entire gallery. Sometimes that's way too loud.
Shot Count
Gallery disturbed 1 times
useRef is the photographer's silent lens β it adjusts settings and remembers values without ever firing the shutter or disturbing the gallery.
Shots Taken
Shots taken silently β gallery doesn't update!
function SilentLens() {
const shotCountRef = useRef(0);
function snap() {
shotCountRef.current += 1;
// No re-render! The gallery stays still.
console.log("Silent shot:", shotCountRef.current);
}
// To see the value, you'd need a separate re-render trigger
}useRef is the photographer's private notebook β it remembers everything but never announces it to the gallery.
useRef has two superpowers: pointing the lens at DOM elements directly, and storing mutable values silently between shots. Try these examples.
Point the camera lens at different subjects β useRef gives you direct access to DOM elements.
const portraitRef = useRef<HTMLInputElement>(null);
const landscapeRef = useRef<HTMLInputElement>(null);
// Point the lens at a subject:
portraitRef.current?.focus();
// In JSX:
<input ref={portraitRef} />Track how many shots (renders) this component has taken β without causing extra renders.
shotCount.current
0
The shot count increments after every render, but incrementing it doesn't cause a new render β no infinite loop!
const shotCount = useRef(0);
useEffect(() => {
shotCount.current += 1;
// Runs after every render,
// but updating the ref does NOT cause another render!
});The interval ID is stored in a ref β changing it doesn't re-render the component.
const exposureRef = useRef<number | null>(null);
// Store the timer ID in a ref β not state!
function startExposure() {
exposureRef.current = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
}
function endExposure() {
clearInterval(exposureRef.current);
exposureRef.current = null;
}These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
function Form() {
const nameRef = useRef("");
return (
<>
<input onChange={(e) => { nameRef.current = e.target.value; }} />
<p>Name: {nameRef.current}</p> {/* Never updates on screen */}
</>
);
}function Counter() {
const countRef = useRef(0);
// Runs on every render, doubles in Strict Mode
countRef.current++;
return <div>{countRef.current}</div>;
}function Box() {
const boxRef = useRef(null);
// ref is null during render β DOM doesn't exist yet
const height = boxRef.current?.offsetHeight; // always 0
return <div ref={boxRef}>Content (height: {height})</div>;
}