⚛️ React Hook: useRef
📖 Quick Summary
useRef
returns a mutable ref object whose.current
property persists across renders.- It does not trigger a re-render when updated.
- Common use cases: accessing DOM elements, storing mutable values, caching values between renders.
- Syntax:tsx
const ref = useRef(initialValue);
🧠 Mental Model
- Think of
useRef
as a sticky note 📝 attached to your component. - It survives re-renders without causing re-renders itself.
.current
is like a box where you can store data that React won’t track for rendering.
🔑 Key Concepts
Persistent Across Renders
- The object returned by
useRef
is stable across renders. - Unlike state, updating
.current
does not trigger re-render.
- The object returned by
Accessing DOM Elements
- Attach
ref
to a DOM element using theref
attribute. - Useful for focusing inputs, measuring elements, controlling animations.
- Attach
Storing Mutable Values
- Keep any mutable value (like interval IDs, previous values) without re-rendering.
Difference vs
useState
useState
triggers a re-render on change.useRef
just stores the value silently.
💻 Code Examples
Example 1: Accessing a DOM Element
import { useRef } from "react";
export default function FocusInput() {
const inputRef = useRef<HTMLInputElement>(null);
const handleFocus = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<div>
<input ref={inputRef} placeholder="Type here..." />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
}
How it works (step‑by‑step):
useRef(null)
creates a ref object with.current = null
.- React assigns
inputRef.current
to the<input>
DOM node after rendering. - Clicking the button calls
handleFocus
, which invokes.focus()
on the input DOM element. - The input gets focus programmatically without re-rendering.
Example 2: Storing Mutable Values (Timer ID)
import { useRef, useState } from "react";
export default function Timer() {
const [count, setCount] = useState(0);
const timerRef = useRef<number | null>(null);
const startTimer = () => {
if (!timerRef.current) {
timerRef.current = window.setInterval(() => {
setCount(c => c + 1);
}, 1000);
}
};
const stopTimer = () => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
};
return (
<div>
<p>Count: {count}</p>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
</div>
);
}
How it works (step‑by‑step):
timerRef.current
starts asnull
.- Clicking Start sets an interval and stores its ID in
timerRef.current
. - Because refs don’t cause re-renders, this update doesn’t refresh the UI.
- Clicking Stop clears the interval using the stored ID and resets
.current
.
Example 3: Storing Previous State Value
import { useState, useRef, useEffect } from "react";
export default function PreviousValue() {
const [count, setCount] = useState(0);
const prevCountRef = useRef<number | null>(null);
useEffect(() => {
prevCountRef.current = count;
}, [count]);
return (
<div>
<p>Now: {count}, Before: {prevCountRef.current}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
How it works (step‑by‑step):
- On first render,
prevCountRef.current = null
. - Every time
count
changes, theuseEffect
updatesprevCountRef.current
to the latest value. - UI shows both current and previous count values.
Example 4: Avoiding Re-Creation of Objects
import { useRef } from "react";
export default function CounterRef() {
const renderCount = useRef(0);
renderCount.current++;
return <p>Component rendered {renderCount.current} times</p>;
}
How it works (step‑by‑step):
- On first render,
renderCount.current = 0
. - Each render increments
renderCount.current
. - Because ref persists between renders, the count continues across renders.
- Unlike state, updating this value does not cause another render → avoids infinite loops.
⚠️ Common Pitfalls & Gotchas
- ❌ Expecting ref updates to trigger re-renders → they don’t.
- ❌ Using refs when state is needed (e.g., UI updates).
- ❌ Forgetting cleanup when storing things like timers or subscriptions.
✅ Best Practices
- Use refs for DOM access and non-UI mutable values.
- Use state when the UI must update in response to value changes.
- Combine with
useEffect
to update refs based on state changes. - Keep refs minimal; don’t overuse them for general data storage.
❓ Interview Q&A
Q1. What is useRef
used for?
A: To persist values across renders without causing re-renders. Commonly used for DOM access, storing mutable values, or caching.
Q2. How is useRef
different from useState
?
A:
useState
updates trigger a re-render.useRef
updates don’t trigger re-render; it just stores the value.
Q3. Can you store a previous state value with useRef
?
A: Yes. By updating the ref in useEffect
, you can capture the previous value between renders.
Q4. How do you access a DOM node with useRef
?
A: Attach it to an element using the ref
attribute. Example:
const inputRef = useRef(null);
<input ref={inputRef} />
Q5. Does updating useRef.current
trigger a re-render?
A: No. That’s the main difference from state. It only changes the stored value.
Q6. Can you use multiple refs in a component?
A: Yes, you can call useRef
multiple times for different DOM nodes or values.