useRef & the DOM
useRef for DOM access and mutable render-persistent values that don't trigger re-renders; forwardRef and pitfalls.
🧑🏫 Sabse pehle — simple mein samjho#
Ref ek dabba (box) hai .current ke saath — usme koi value ya DOM element pakad ke rakh sakte ho. State ke ulta: ref badalne se re-render NAHI hota, chupchaap update hota hai. Common use: input pe focus karna, ya render ke beech koi value yaad rakhna. Jaise ghar ki chaabi ek locker mein — jab chahiye tab nikaal lo, par locker badalne se ghar nahi hilta.
function Search() {
const inputRef = useRef(null); // dabba, shuru mein null
return (
<>
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</>
);
}
Yaad rakho: ref value pakadta hai bina re-render kiye — DOM chhoone ya value yaad rakhne ke liye.
Two jobs of useRef 🎯#
useRef(initial) returns a stable { current } object that persists across renders. Two uses:
- Access a DOM node — attach the ref to a JSX element's
refprop. - Hold a mutable value across renders — like an instance variable, without triggering a re-render when it changes.
const ref = useRef(null); // stable object, same identity every render
ref.current = anything; // mutating does NOT re-render
Accessing DOM nodes#
React sets ref.current to the DOM node after commit, and back to null on unmount.
function SearchBox() {
const inputRef = useRef(null);
useEffect(() => { inputRef.current.focus(); }, []); // focus on mount
return <input ref={inputRef} placeholder="Search…" />;
}
Common DOM uses: focus, scroll into view, measure size/position, and integrating imperative libraries.
inputRef.current.focus();
boxRef.current.scrollIntoView({ behavior: "smooth" });
const { width, height } = boxRef.current.getBoundingClientRect(); // measure
Real-world example: autofocus the search bar (Flipkart/Amazon)#
Product-search bars on Flipkart/Amazon autofocus the moment the page loads, so the user can start typing immediately without clicking first.
function ProductSearchBar() {
const searchRef = useRef(null);
useEffect(() => {
searchRef.current.focus(); // cursor ready to type, no click needed
}, []);
return (
<input
ref={searchRef}
type="search"
placeholder="Search for products, brands and more"
/>
);
}
🟢 The pattern is always the same: create the ref, attach it to the JSX element, then reach into .current inside an effect (after commit) — never during render itself.
Mutable value that survives renders#
Unlike state, updating a ref doesn't re-render — ideal for values you read/write imperatively but that shouldn't drive the UI (timer IDs, previous values, latest-callback holders).
function Timer() {
const idRef = useRef(null); // holds interval id across renders
const start = () => { idRef.current = setInterval(tick, 1000); };
const stop = () => clearInterval(idRef.current);
// ...
}
useState |
useRef |
|
|---|---|---|
| Change triggers re-render | Yes | No |
| Value persists across renders | Yes | Yes |
| Read latest synchronously | After re-render | Immediately via .current |
| Use for | UI data | DOM nodes, non-UI mutable values |
Real-world example: tracking the previous render's value#
Sometimes you want to compare the current value to what it was on the last render — e.g. showing a "price dropped" badge only when a product's price actually decreases — without that comparison itself causing another re-render.
function PriceTag({ price }) {
const prevPriceRef = useRef(price);
const dropped = prevPriceRef.current > price;
useEffect(() => {
prevPriceRef.current = price; // update AFTER paint, for the next render's comparison
}, [price]);
return <span>{dropped ? "🔻" : ""} ₹{price}</span>;
}
🟢 prevPriceRef never appears directly in the JSX and updating it never re-renders anything — exactly why a ref, not state, is the right tool for "remember but don't announce."
useRef vs useState side by side#
flowchart LR
subgraph State["useState: setCount(n)"]
S1["setCount called"] --> S2["React schedules a re-render"]
S2 --> S3["Component function runs again"]
S3 --> S4["UI reflects the new value"]
end
subgraph Ref["useRef: countRef.current = n"]
R1["countRef.current mutated"] --> R2["No re-render scheduled"]
R2 --> R3["Value updated silently"]
R3 --> R4["UI unchanged until something else triggers a render"]
end
forwardRef#
Refs aren't passed through like normal props. To let a parent ref reach a child's DOM node, wrap the child in forwardRef.
const Input = forwardRef((props, ref) => <input ref={ref} {...props} />);
function Parent() {
const ref = useRef(null);
return <Input ref={ref} />; // ref now points to the inner <input>
}
⚠️ In React 19, ref can be received as a normal prop in function components, making forwardRef largely unnecessary (it's being deprecated). Use useImperativeHandle to expose a curated imperative API instead of the raw node.
⚠️ Don't read/write refs during render#
Rendering must be pure. Mutating or reading ref.current during render is unpredictable because React may render without committing. Do ref work in effects or event handlers, where the DOM is committed.
function Bad() {
const ref = useRef(0);
ref.current++; // ⚠️ side effect during render
return <div>{ref.current}</div>;
}
flowchart LR
R["Render (pure)"] --> C["Commit to DOM"]
C --> P["Paint"]
P --> E["Effect / event handler"]
E -->|"read & write ref.current here"| OK["Safe"]
R -.->|"mutate ref here"| BAD["Unsafe ⚠️"]
Interview Q&A#
Q1. What are the two main uses of useRef?
Accessing a DOM node via the ref prop, and storing a mutable value that persists across renders without causing a re-render.
Q2. How does useRef differ from useState?
Both persist across renders, but changing a ref doesn't re-render and is applied synchronously via .current; changing state schedules a re-render.
Q3. Why doesn't mutating ref.current update the UI?
React doesn't track ref mutations. The { current } object is stable and changes to it bypass the render cycle entirely — that's the point.
Q4. What is forwardRef for?
It lets a parent's ref pass through a component to reach a child's DOM node, since ref isn't a normal prop. In React 19 ref is a regular prop, so forwardRef is being deprecated.
Q5. Why avoid reading/writing refs during render?
Render must be pure and can run without committing; touching ref.current there yields inconsistent results. Do it in effects or event handlers after commit.