EN/CH Mode
BRUCE_FE React Interview Notes - useRef Usage and Implementation
Deep dive into React useRef's various uses, internal implementation, and usage techniques. Master accessing DOM elements with useRef, storing variables without triggering re-renders, understand its differences from useState, and common useRef interview questions and answers.
EN/CH Mode

Lazy to read articles? Then watch videos!
Introduction to useRef and Its Utility
useRef is one of React's Hooks, used to create a mutable reference that persists throughout a component's lifecycle. Its key feature is: when the ref's content changes, it doesn't trigger a component re-render.
useRef vs useState in Render Cycles
State Changes and Rendering Relationship:
useRef
Render 1: ref.current = 0
Modify value: ref.current = 1
→ No re-render triggered
Still render 1: ref.current = 1
useState
Render 1: [value=0, setValue]
Modify value: setValue(1)
→ Triggers re-render
Render 2: [value=1, setValue]
Reference Consistency Comparison:
useRef Maintains Same Reference
useRef returns the same reference object throughout the component's lifecycle. Even when the component re-renders, the ref object's identity remains unchanged, only the current property's value may change.
// First render
const ref = useRef(0); // ref = { current: 0 }
// After multiple renders, ref is still the same object
console.log(ref === previousRenderRef); // trueuseState Creates New Value Each Render
useState creates a new snapshot of the current state in each render. Although the state value might be the same, the state variable in each render is an independent constant.
// Render 1
const [count, setCount] = useState(0);
// count in this render is a snapshot of 0
// Render 2 (after setCount(1))
const [count, setCount] = useState(0);
// count in this render is a new snapshot of 1Real Example: Counter Without Re-rendering
function SilentCounter() {
const [, forceUpdate] = useState({});
const countRef = useRef(0);
return (
<>
<p>Count: {countRef.current}</p>
<button onClick={() => {
// Increment count without re-rendering
countRef.current += 1;
console.log("Count increased to:", countRef.current);
}}>
Increment Count (No UI Update)
</button>
<button onClick={() => forceUpdate({})}>
Manual UI Update
</button>
</>
);
}Three Main Uses of useRef
1. Accessing DOM Elements
This is the most common use of useRef, directly accessing DOM nodes for imperative operations (e.g., focus, video playback, size calculations).
- 1. Need to manipulate DOM elements
- 2. Store data that doesn't affect UI (timer IDs, etc.)
- 3. Need to remember values without triggering re-renders
- 1. Create a plain JavaScript object with a current property
- 2. Ensure this object maintains a stable reference between renders
- 3. Provide no active notification mechanism when ref content changes
function AutoPlayVideo() {
const videoRef = useRef(null);
useEffect(() => {
// Auto-play when component mounts
videoRef.current.play();
return () => {
// Pause when component unmounts
videoRef.current.pause();
};
}, []);
return <video ref={videoRef} src="https://example.com/video.mp4" />;
}2. Store Mutable Values (Without Triggering Re-renders)
useRef can store values of any type and updating them won't trigger re-renders, making it ideal for storing data that doesn't need to participate in render calculations.
function IntervalCounter() {
const [count, setCount] = useState(0);
// Store interval ID for cleanup
const intervalRef = useRef(null);
// Record count value from last render
const previousCountRef = useRef(0);
useEffect(() => {
// Save previous count (runs after each render)
previousCountRef.current = count;
// Set up interval
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => {
// Clean up interval
clearInterval(intervalRef.current);
};
}, []);
return (
<div>
Now: {count}, Previous: {previousCountRef.current}
</div>
);
}3. Cache Computation Results (Similar to Memoization)
When you need to store results of expensive calculations but don't want to recalculate on every re-render, useRef can serve as a lightweight alternative.
function SearchResults({ query }) {
const [results, setResults] = useState([]);
// Use ref to cache previous search results
const prevQueryRef = useRef('');
const cachedResultsRef = useRef({});
useEffect(() => {
// If query hasn't changed or we have cached results, use them
if (query === prevQueryRef.current && cachedResultsRef.current[query]) {
setResults(cachedResultsRef.current[query]);
return;
}
// Otherwise perform search and store results
fetchSearchResults(query).then(data => {
setResults(data);
prevQueryRef.current = query;
cachedResultsRef.current[query] = data;
});
}, [query]);
return (
<ul>
{results.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}useRef vs useState: When to Use Which?
Key Differences
| Feature | useState | useRef |
|---|---|---|
| Triggers Re-render on Update | Yes ✅ | No ❌ |
| Value Access Method | Direct Variable Access | Through .current Property |
| Update Method | Using Setter Function | Directly Modify .current |
| Maintains Value Across Re-renders | Yes ✅ | Yes ✅ |
| Applicable Scenarios | Data Needed for UI Display | Values Not Involved in Rendering or DOM References |
Selection Guide
When to use useState:
- 1. When data changes need to be immediately reflected in the UI
- 2. When you need to trigger component re-renders to update the view
- 3. When the value affects render results or other state/effect dependencies
When to use useRef:
- 1. Store values that don't affect render output (timer IDs, previous props, etc.)
- 2. Need direct access to DOM elements
- 3. Need to store values that persist throughout component lifecycle without triggering updates
- 4. Optimize performance by avoiding unnecessary re-renders
// Consider this counter example, showing two different implementations
function CounterExample() {
// Updates will trigger re-render
const [stateCount, setStateCount] = useState(0);
// Updates will not trigger re-render
const refCount = useRef(0);
return (
<>
<div>
<h3>useState Counter: {stateCount}</h3>
<button onClick={() => setStateCount(c => c + 1)}>
Increment State Count
</button>
</div>
<div>
<h3>useRef Counter: {refCount.current}</h3>
<button
onClick={() => {
refCount.current += 1;
// Note: Manual force re-render needed to see updates
console.log('useRef value updated, but UI won't update automatically');
}}
>
Increment Ref Count (No UI Update)
</button>
</div>
</>
);
}Internal Implementation of useRef
Understanding useRef's implementation helps us better grasp its behavioral characteristics. Inside React, useRef's implementation is quite simple:
// Simplified implementation of useRef inside React
function useRef(initialValue) {
const hookIndex = currentComponent.currentHookIndex++;
if (!currentComponent.hooks[hookIndex]) {
currentComponent.hooks[hookIndex] = { current: initialValue };
}
return currentComponent.hooks[hookIndex];
}In essence, the core of useRef's implementation lies in:
- 1. Creating a plain JavaScript object with a
currentproperty - 2. Ensuring this object maintains a stable reference between renders
- 3. Not providing any active notification mechanism when ref content changes
In React's Fiber architecture, useRef's state is also stored in the Fiber node's memoizedState as part of the Hook linked list:
// Hook structure in React Fiber
{
memoizedState: {
/* useState Hook node */
// ...
next: {
// useRef Hook node
memoizedState: { current: initialValue },
next: {
/* Next Hook */
// ...
}
}
}
}Advanced Techniques and Patterns
Using useRef to Store Previous Props or State
In some scenarios, when you need to compare current values with previous ones, useRef is the ideal tool:
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
// Update ref value after each render
ref.current = value;
}, [value]);
// Return the previous value
return ref.current;
}
function ProfileUpdater({ userId }) {
const prevUserId = usePrevious(userId);
useEffect(() => {
// Only execute when user changes
if (prevUserId !== userId) {
console.log(`User switched from ${prevUserId} to ${userId}`);
fetchUserData(userId);
}
}, [userId, prevUserId]);
// Rest of the component
}Conditional DOM References
When elements might not exist or are conditionally rendered, refs need to be handled carefully:
function ConditionalRefExample({ showInput }) {
const inputRef = useRef(null);
// Use with useLayoutEffect to ensure safe DOM operations
useLayoutEffect(() => {
// Safely check if element exists
if (showInput && inputRef.current) {
inputRef.current.focus();
}
}, [showInput]);
return (
<div>
{showInput && (
<input ref={inputRef} type="text" placeholder="I will auto-focus" />
)}
{/* ref.current is null when element doesn't exist */}
</div>
);
}🔥 Common Interview Questions
(1) What is useRef? What are its main uses?
Answer: useRef is a React Hook that creates a "memory box" where its contents persist even when the component re-renders.
useRef Box
.current
Modifications don't trigger re-renders
Main uses:
- 1. Access DOM elements (e.g., auto-focus input fields)
- 2. Store data that doesn't need to trigger re-renders
- 3. Remember previous state values
- 4. Store values like timer IDs that need to persist across render cycles
(2) What's the difference between useRef and useState?
useState
Modify value → Triggers re-render
Update using setter function
Direct access: value
useRef
Modify value → No re-render
Directly modify .current
Access: ref.current
(3) Why doesn't modifying useRef trigger re-renders?
Answer: This is related to React's design:
React Rendering Mechanism Diagram
useState
↓
Notify React to update
↓
Trigger re-render
useRef
↓
Silently update .current
↓
React unaware of change
In simple terms:
- 1. useState has a 'notification mechanism' to tell React UI updates are needed
- 2. useRef is just a container, modifications don't notify React
- 3. React only cares about update requests through official channels (like setState)