DEV Community

M Jagadeesh
M Jagadeesh

Posted on

How Advanced React Developers Should Think About Hooks

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>;
}
Enter fullscreen mode Exit fullscreen mode

React converts JSX into Virtual DOM.


2. Re-render UI When Data Changes

setState(...)
Enter fullscreen mode Exit fullscreen mode

Whenever state changes:

State Changed
     ↓
Component Re-renders
     ↓
Virtual DOM Generated
     ↓
Diffing
     ↓
Real DOM Updated
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Output:

render
render
render
render
Enter fullscreen mode Exit fullscreen mode

Every state change creates a new render.

This means:

const name = "John";
Enter fullscreen mode Exit fullscreen mode

is recreated every render.

Also:

const user = {};
Enter fullscreen mode Exit fullscreen mode

is recreated every render.

Also:

const handleClick = () => {};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

When React re-renders:

const [count] = useState();
const [name] = useState();
Enter fullscreen mode Exit fullscreen mode

React relies on hook ordering.

This is why:

if(condition){
  useState();
}
Enter fullscreen mode Exit fullscreen mode

is forbidden.

React would lose track of state positions.


State Updates Are Scheduled

Developers think:

setCount(5);
console.log(count);
Enter fullscreen mode Exit fullscreen mode

prints:

5
Enter fullscreen mode Exit fullscreen mode

Actually:

0
Enter fullscreen mode Exit fullscreen mode

Why?

Because React schedules updates.

setCount
     ↓
Update Queue
     ↓
React Scheduler
     ↓
Render
Enter fullscreen mode Exit fullscreen mode

This is critical in React 18 concurrent rendering.


Functional Updates

Bad:

setCount(count + 1);
setCount(count + 1);
Enter fullscreen mode Exit fullscreen mode

Result:

1
Enter fullscreen mode Exit fullscreen mode

Not:

2
Enter fullscreen mode Exit fullscreen mode

Because both updates use the same stale value.

Correct:

setCount(prev => prev + 1);
setCount(prev => prev + 1);
Enter fullscreen mode Exit fullscreen mode

Result:

2
Enter fullscreen mode Exit fullscreen mode

Production systems frequently contain bugs caused by stale closures.


useEffect Deep Dive

Most developers misuse useEffect.

Many developers treat it as:

ComponentDidMount
Enter fullscreen mode Exit fullscreen mode

This is incorrect.

The real mental model:

Synchronize React
with External Systems
Enter fullscreen mode Exit fullscreen mode

Examples:

  • APIs
  • WebSocket
  • Local Storage
  • Browser APIs
  • Analytics
  • Event Listeners

Effect Lifecycle

React rendering:

Render
↓
Commit
↓
Paint
↓
useEffect
Enter fullscreen mode Exit fullscreen mode

Notice:

Paint happens first
Enter fullscreen mode Exit fullscreen mode

This explains why useEffect never blocks UI rendering.


Dependency Array Explained Properly

Example:

useEffect(() => {

}, [count]);
Enter fullscreen mode Exit fullscreen mode

React compares:

Previous count
Current count
Enter fullscreen mode Exit fullscreen mode

using:

Object.is()
Enter fullscreen mode Exit fullscreen mode

If changed:

Run Effect
Enter fullscreen mode Exit fullscreen mode

Otherwise:

Skip Effect
Enter fullscreen mode Exit fullscreen mode

Why Infinite Loops Happen

Example:

useEffect(() => {

  fetchUsers();

}, [fetchUsers]);
Enter fullscreen mode Exit fullscreen mode

Problem:

const fetchUsers = () => {};
Enter fullscreen mode Exit fullscreen mode

creates a new function every render.

React sees:

Old Function !== New Function
Enter fullscreen mode Exit fullscreen mode

Effect runs again.

Infinite loop.


Production Pattern

Bad:

useEffect(() => {
  fetch(...)
});
Enter fullscreen mode Exit fullscreen mode

Good:

useEffect(() => {

  let active = true;

  async function load(){

    const data =
      await fetch(...);

    if(active){
      setUsers(data);
    }
  }

  load();

  return () => {
    active = false;
  };

}, []);
Enter fullscreen mode Exit fullscreen mode

Prevents race conditions.


useRef Deep Dive

Many developers think:

DOM Reference Hook
Enter fullscreen mode Exit fullscreen mode

Actually:

Persistent Mutable Container
Enter fullscreen mode Exit fullscreen mode

DOM references are just one use case.


Internal Structure

const ref = useRef(0);
Enter fullscreen mode Exit fullscreen mode

Creates:

{
  current: 0
}
Enter fullscreen mode Exit fullscreen mode

React keeps the same object between renders.


Why useRef Doesn't Re-render

ref.current = 100;
Enter fullscreen mode Exit fullscreen mode

React doesn't track:

current
Enter fullscreen mode Exit fullscreen mode

changes.

Therefore:

No Re-render
Enter fullscreen mode Exit fullscreen mode

Real Production Uses

Debouncing

const timeoutRef =
  useRef();
Enter fullscreen mode Exit fullscreen mode

Previous Values

const prev =
  useRef();
Enter fullscreen mode Exit fullscreen mode

Abort Controllers

const controllerRef =
  useRef();
Enter fullscreen mode Exit fullscreen mode

WebSocket Instances

const socketRef =
  useRef();
Enter fullscreen mode Exit fullscreen mode

useMemo Deep Dive

One of the most misunderstood hooks.

Many developers think:

useMemo = Faster
Enter fullscreen mode Exit fullscreen mode

Wrong.

Sometimes:

useMemo = Slower
Enter fullscreen mode Exit fullscreen mode

because React must:

  • Store cache
  • Compare dependencies
  • Maintain references

When useMemo Helps

Expensive calculations:

const filteredUsers =
  useMemo(() => {

    return users.filter(
      u => heavyLogic(u)
    );

  }, [users]);
Enter fullscreen mode Exit fullscreen mode

Without memoization:

Runs Every Render
Enter fullscreen mode Exit fullscreen mode

With memoization:

Runs Only When users Changes
Enter fullscreen mode Exit fullscreen mode

When NOT To Use

Bad:

const value =
  useMemo(
    () => count + 1,
    [count]
  );
Enter fullscreen mode Exit fullscreen mode

Calculation cost:

0.000001 ms
Enter fullscreen mode Exit fullscreen mode

Memoization overhead:

Higher
Enter fullscreen mode Exit fullscreen mode

Rule

Ask:

Is recalculation more expensive
than caching?
Enter fullscreen mode Exit fullscreen mode

Only then use useMemo.


useCallback Deep Dive

Many developers misuse it.


Without:

const handleClick = () => {};
Enter fullscreen mode Exit fullscreen mode

Every render:

New Function Created
Enter fullscreen mode Exit fullscreen mode

With:

const handleClick =
  useCallback(() => {

  }, []);
Enter fullscreen mode Exit fullscreen mode

React returns same function reference.


Why Function Identity Matters

Consider:

<Child
  onClick={handleClick}
/>
Enter fullscreen mode Exit fullscreen mode

and

const Child =
  React.memo(...)
Enter fullscreen mode Exit fullscreen mode

React compares props.

Without useCallback:

Function Changed
Enter fullscreen mode Exit fullscreen mode

Child re-renders.


With useCallback:

Function Same
Enter fullscreen mode Exit fullscreen mode

Child skips render.


useContext Deep Dive

Context is not state management.

This misconception causes huge performance issues.

Context only provides:

Dependency Injection
Enter fullscreen mode Exit fullscreen mode

for React.


Problem

<App>
  <Navbar>
     <UserMenu>
Enter fullscreen mode Exit fullscreen mode

Passing:

user
Enter fullscreen mode Exit fullscreen mode

through every component.


Solution

UserContext
Enter fullscreen mode Exit fullscreen mode

Performance Problem

When Context changes:

ALL Consumers Re-render
Enter fullscreen mode Exit fullscreen mode

Example:

<UserContext.Provider
 value={user}
>
Enter fullscreen mode Exit fullscreen mode

If:

user.name changes
Enter fullscreen mode Exit fullscreen mode

Every consumer re-renders.


Optimization

Split contexts.

Bad:

GlobalContext
Enter fullscreen mode Exit fullscreen mode

Good:

UserContext
ThemeContext
LanguageContext
Enter fullscreen mode Exit fullscreen mode

useReducer Deep Dive

Think:

useState for simple state

useReducer for state machines
Enter fullscreen mode Exit fullscreen mode

Example:

Login Form

Idle
Loading
Success
Error
Enter fullscreen mode Exit fullscreen mode

Represented as:

{
 status:"loading"
}
Enter fullscreen mode Exit fullscreen mode

Actions:

LOGIN_START
LOGIN_SUCCESS
LOGIN_FAILED
Enter fullscreen mode Exit fullscreen mode

Much easier than managing multiple useState calls.


useLayoutEffect Deep Dive

Execution:

Render
↓
DOM Update
↓
useLayoutEffect
↓
Paint
Enter fullscreen mode Exit fullscreen mode

Notice:

Before Paint
Enter fullscreen mode Exit fullscreen mode

This means:

useLayoutEffect(() => {
  measureElement();
});
Enter fullscreen mode Exit fullscreen mode

can block rendering.

Use carefully.


React 18 Hooks

useTransition

Allows lower-priority updates.

Imagine:

Typing Search Input
Enter fullscreen mode Exit fullscreen mode

and:

Filtering 100,000 Rows
Enter fullscreen mode Exit fullscreen mode

Without transition:

Typing Lags
Enter fullscreen mode Exit fullscreen mode

With:

startTransition(() => {
  setResults(...)
});
Enter fullscreen mode Exit fullscreen mode

Typing remains responsive.


useDeferredValue

Think:

Debounce
without timers
Enter fullscreen mode Exit fullscreen mode

Example:

const deferredSearch =
  useDeferredValue(search);
Enter fullscreen mode Exit fullscreen mode

User types:

r
re
rea
reac
react
Enter fullscreen mode Exit fullscreen mode

UI stays responsive.

Heavy rendering happens later.


Custom Hooks Architecture

The true power of React Hooks.

Bad:

1200-line component
Enter fullscreen mode Exit fullscreen mode

Good:

useAuth()
useApi()
useUsers()
usePagination()
useSearch()
Enter fullscreen mode Exit fullscreen mode

Each hook:

Owns
Its Logic
Enter fullscreen mode Exit fullscreen mode

while UI remains clean.


Most Common Hook Mistakes in Production

Mistake 1

Using useEffect for calculations.

Bad:

useEffect(() => {
  setTotal(price * qty);
});
Enter fullscreen mode Exit fullscreen mode

Use:

const total =
  price * qty;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

memoizes values.

useCallback
Enter fullscreen mode Exit fullscreen mode

memoizes functions.


Difference between useEffect and useLayoutEffect?

useEffect
After Paint

useLayoutEffect
Before Paint
Enter fullscreen mode Exit fullscreen mode

Top comments (0)