How Advanced React Developers Should Think About Hooks
Before discussing individual hooks, understand one fundamental concept:
React Has Only Two Jobs
1. Render UI
function User() {
return <h1>Hello</h1>;
}
React converts JSX into Virtual DOM.
2. Re-render UI When Data Changes
setState(...)
Whenever state changes:
State Changed
↓
Component Re-renders
↓
Virtual DOM Generated
↓
Diffing
↓
Real DOM Updated
Hooks are mechanisms that allow React to:
- Remember data
- Execute side effects
- Share data
- Optimize rendering
- Control scheduling
Everything in React Hooks revolves around these five responsibilities.
Understanding Render Cycles First
Most developers learn hooks without understanding rendering.
This creates confusion later.
Example:
function App() {
const [count,setCount] =
useState(0);
console.log("render");
return (
<button
onClick={() =>
setCount(count + 1)
}
>
{count}
</button>
);
}
Output:
render
render
render
render
Every state change creates a new render.
This means:
const name = "John";
is recreated every render.
Also:
const user = {};
is recreated every render.
Also:
const handleClick = () => {};
is recreated every render.
This single concept explains:
- useMemo
- useCallback
- React.memo
- Performance optimization
useState Deep Dive
Most tutorials say:
useState stores state.
That's true but incomplete.
Internally React stores state values in a linked list attached to the component Fiber.
Conceptually:
Component Fiber
State Slot 1
State Slot 2
State Slot 3
When React re-renders:
const [count] = useState();
const [name] = useState();
React relies on hook ordering.
This is why:
if(condition){
useState();
}
is forbidden.
React would lose track of state positions.
State Updates Are Scheduled
Developers think:
setCount(5);
console.log(count);
prints:
5
Actually:
0
Why?
Because React schedules updates.
setCount
↓
Update Queue
↓
React Scheduler
↓
Render
This is critical in React 18 concurrent rendering.
Functional Updates
Bad:
setCount(count + 1);
setCount(count + 1);
Result:
1
Not:
2
Because both updates use the same stale value.
Correct:
setCount(prev => prev + 1);
setCount(prev => prev + 1);
Result:
2
Production systems frequently contain bugs caused by stale closures.
useEffect Deep Dive
Most developers misuse useEffect.
Many developers treat it as:
ComponentDidMount
This is incorrect.
The real mental model:
Synchronize React
with External Systems
Examples:
- APIs
- WebSocket
- Local Storage
- Browser APIs
- Analytics
- Event Listeners
Effect Lifecycle
React rendering:
Render
↓
Commit
↓
Paint
↓
useEffect
Notice:
Paint happens first
This explains why useEffect never blocks UI rendering.
Dependency Array Explained Properly
Example:
useEffect(() => {
}, [count]);
React compares:
Previous count
Current count
using:
Object.is()
If changed:
Run Effect
Otherwise:
Skip Effect
Why Infinite Loops Happen
Example:
useEffect(() => {
fetchUsers();
}, [fetchUsers]);
Problem:
const fetchUsers = () => {};
creates a new function every render.
React sees:
Old Function !== New Function
Effect runs again.
Infinite loop.
Production Pattern
Bad:
useEffect(() => {
fetch(...)
});
Good:
useEffect(() => {
let active = true;
async function load(){
const data =
await fetch(...);
if(active){
setUsers(data);
}
}
load();
return () => {
active = false;
};
}, []);
Prevents race conditions.
useRef Deep Dive
Many developers think:
DOM Reference Hook
Actually:
Persistent Mutable Container
DOM references are just one use case.
Internal Structure
const ref = useRef(0);
Creates:
{
current: 0
}
React keeps the same object between renders.
Why useRef Doesn't Re-render
ref.current = 100;
React doesn't track:
current
changes.
Therefore:
No Re-render
Real Production Uses
Debouncing
const timeoutRef =
useRef();
Previous Values
const prev =
useRef();
Abort Controllers
const controllerRef =
useRef();
WebSocket Instances
const socketRef =
useRef();
useMemo Deep Dive
One of the most misunderstood hooks.
Many developers think:
useMemo = Faster
Wrong.
Sometimes:
useMemo = Slower
because React must:
- Store cache
- Compare dependencies
- Maintain references
When useMemo Helps
Expensive calculations:
const filteredUsers =
useMemo(() => {
return users.filter(
u => heavyLogic(u)
);
}, [users]);
Without memoization:
Runs Every Render
With memoization:
Runs Only When users Changes
When NOT To Use
Bad:
const value =
useMemo(
() => count + 1,
[count]
);
Calculation cost:
0.000001 ms
Memoization overhead:
Higher
Rule
Ask:
Is recalculation more expensive
than caching?
Only then use useMemo.
useCallback Deep Dive
Many developers misuse it.
Without:
const handleClick = () => {};
Every render:
New Function Created
With:
const handleClick =
useCallback(() => {
}, []);
React returns same function reference.
Why Function Identity Matters
Consider:
<Child
onClick={handleClick}
/>
and
const Child =
React.memo(...)
React compares props.
Without useCallback:
Function Changed
Child re-renders.
With useCallback:
Function Same
Child skips render.
useContext Deep Dive
Context is not state management.
This misconception causes huge performance issues.
Context only provides:
Dependency Injection
for React.
Problem
<App>
<Navbar>
<UserMenu>
Passing:
user
through every component.
Solution
UserContext
Performance Problem
When Context changes:
ALL Consumers Re-render
Example:
<UserContext.Provider
value={user}
>
If:
user.name changes
Every consumer re-renders.
Optimization
Split contexts.
Bad:
GlobalContext
Good:
UserContext
ThemeContext
LanguageContext
useReducer Deep Dive
Think:
useState for simple state
useReducer for state machines
Example:
Login Form
Idle
Loading
Success
Error
Represented as:
{
status:"loading"
}
Actions:
LOGIN_START
LOGIN_SUCCESS
LOGIN_FAILED
Much easier than managing multiple useState calls.
useLayoutEffect Deep Dive
Execution:
Render
↓
DOM Update
↓
useLayoutEffect
↓
Paint
Notice:
Before Paint
This means:
useLayoutEffect(() => {
measureElement();
});
can block rendering.
Use carefully.
React 18 Hooks
useTransition
Allows lower-priority updates.
Imagine:
Typing Search Input
and:
Filtering 100,000 Rows
Without transition:
Typing Lags
With:
startTransition(() => {
setResults(...)
});
Typing remains responsive.
useDeferredValue
Think:
Debounce
without timers
Example:
const deferredSearch =
useDeferredValue(search);
User types:
r
re
rea
reac
react
UI stays responsive.
Heavy rendering happens later.
Custom Hooks Architecture
The true power of React Hooks.
Bad:
1200-line component
Good:
useAuth()
useApi()
useUsers()
usePagination()
useSearch()
Each hook:
Owns
Its Logic
while UI remains clean.
Most Common Hook Mistakes in Production
Mistake 1
Using useEffect for calculations.
Bad:
useEffect(() => {
setTotal(price * qty);
});
Use:
const total =
price * qty;
Mistake 2
Overusing useMemo.
Mistake 3
Overusing Context.
Mistake 4
Ignoring dependency arrays.
Mistake 5
Storing derived state.
Interview Questions Senior Developers Should Know
Why does React require hooks to be called in the same order?
Because React maps hook state using positional indexing inside Fiber nodes.
Why does useRef not trigger re-renders?
Because React doesn't track mutations on the current property.
Difference between useMemo and useCallback?
useMemo
memoizes values.
useCallback
memoizes functions.
Difference between useEffect and useLayoutEffect?
useEffect
After Paint
useLayoutEffect
Before Paint
Top comments (0)