Skip to main content

Command Palette

Search for a command to run...

React Hooks Handbook

Updated
•10 min read•View as Markdown

A Practical Guide to useState, useEffect, useContext, useReducer, useRef, useMemo, useLayoutEffect & Custom Hooks

Prerequisites: React Components, Props, State Goal: Understand why each Hook exists, when to use it, and how they work together.


Why Were Hooks Introduced?

Before React 16.8, only Class Components could use state and lifecycle methods.

Problems:

  • Large, complex classes

  • this keyword confusion

  • Logic split across lifecycle methods

  • Difficult to reuse stateful logic

Hooks solved this by allowing Function Components to use state, side effects, context, refs, and reusable logic.

Benefits:

  • Cleaner code

  • Better logic reuse

  • Easier testing

  • Simpler components

  • Modern React architecture


Rules of Hooks

Always:

  • Call Hooks at the top level.

  • Call Hooks only inside React components or custom hooks.

❌ Wrong

if (loggedIn) {
    useEffect(() => {});
}

✅ Correct

useEffect(() => {});

React depends on Hooks being called in the same order every render.


1. useState

Why does it exist?

Normal variables reset after every render.

let count = 0;

React forgets them.

useState gives components memory.


Syntax

const [count, setCount] = useState(0);
  • count → current value

  • setCount() → updates state

  • 0 → initial value


Flow

User Click

↓

setCount()

↓

State Changes

↓

Component Re-renders

↓

Updated UI

Example

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <>
      <h2>{count}</h2>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </>
  );
}

Multiple States

const [name, setName] = useState("");
const [age, setAge] = useState(20);
const [darkMode, setDarkMode] = useState(false);

Common Uses

  • Forms

  • Counters

  • Theme toggle

  • Modal visibility

  • Search input

  • Loading state


Mistakes

❌

count++;

✅

setCount(count + 1);

2. useEffect

Why does it exist?

Components should describe UI.

Sometimes we must interact with external systems.

Examples:

  • API requests

  • Timers

  • Event listeners

  • Local Storage

  • WebSockets

These are called Side Effects.


Syntax

useEffect(() => {

}, []);

Mental Model

Think:

Synchronize React with something outside React.

Not:

Run after render.


Fetch Data

useEffect(() => {
  fetch("/api/users")
    .then(res => res.json())
    .then(setUsers);
}, []);

Timer

useEffect(() => {
  const id = setInterval(() => {
    console.log("Tick");
  }, 1000);

  return () => clearInterval(id);
}, []);

Event Listener

useEffect(() => {
  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);

Dependency Array

Runs once

useEffect(() => {}, []);

Runs when user changes

useEffect(() => {}, [user]);

Runs every render

useEffect(() => {});

Infinite Loop

❌

useEffect(() => {
  setCount(count + 1);
}, [count]);

3. useContext

Why does it exist?

To avoid Prop Drilling.

Without Context

App

↓

Dashboard

↓

Navbar

↓

Profile

↓

Avatar

Every component passes

user

With Context

Provider

↓

Any Component

↓

useContext()

Example

Create Context

const ThemeContext = createContext();

Provide

<ThemeContext.Provider value="dark">
  <App />
</ThemeContext.Provider>

Consume

const theme = useContext(ThemeContext);

Good Uses

  • Theme

  • Authentication

  • Language

  • User Preferences


Avoid

  • Search input

  • Form fields

  • Local component state


4. useReducer

Why does it exist?

Sometimes state becomes complicated.

Instead of

setState(...)
setState(...)
setState(...)

React lets us manage updates centrally.


Mental Model

Current State

↓

Action

↓

Reducer

↓

New State

Syntax

const [state, dispatch] = useReducer(
  reducer,
  initialState
);

Example

const initialState = { count: 0 };

function reducer(state, action) {
  switch(action.type) {

    case "increment":
      return {
        count: state.count + 1
      };

    case "decrement":
      return {
        count: state.count - 1
      };

    default:
      return state;
  }
}

Usage

dispatch({
  type: "increment"
});

Best For

  • Shopping cart

  • Authentication

  • Multi-step forms

  • Complex objects

  • Dashboards


useState vs useReducer

useState useReducer
Simple state Complex state
Small components Large logic
Few updates Many actions

5. useRef

Why does it exist?

Sometimes we need values that:

  • Persist between renders

  • Do NOT trigger re-render

That's useRef.


Syntax

const inputRef = useRef(null);

DOM Example

const inputRef = useRef();

<button
onClick={()=>{
inputRef.current.focus();
}}>
Focus
</button>

<input ref={inputRef}/>

Store Mutable Values

const renderCount = useRef(0);

renderCount.current++;

UI doesn't update.


Common Uses

  • Focus input

  • Timers

  • Previous values

  • DOM manipulation


6. useMemo

Why?

Expensive calculations should not run every render.


Without useMemo

Render

↓

Sort 10,000 Items

↓

Render Again

↓

Sort Again

With useMemo

Render

↓

Calculate Once

↓

Reuse Result

Syntax

const sorted = useMemo(() => {

    return products.sort(...);

}, [products]);

Example

const total = useMemo(() => {

 return cart.reduce(
 (sum,item)=>sum+item.price,
 0
 );

}, [cart]);

Use Cases

  • Filtering

  • Sorting

  • Statistics

  • Expensive calculations


Don't Use For

2 + 2

age + 1

name.toUpperCase()

7. useLayoutEffect

Why?

Sometimes React updates the DOM,

but we must measure or modify it before the browser paints.


Flow

Render

↓

DOM Updated

↓

useLayoutEffect

↓

Browser Paint

useEffect

Paint

↓

Effect

Example

useLayoutEffect(() => {

    box.current.style.height="200px";

}, []);

Useful for

  • Measuring elements

  • Scroll position

  • Animations

  • Tooltips


useEffect vs useLayoutEffect

useEffect useLayoutEffect
After Paint Before Paint
Async feeling Blocks paint
Most side effects DOM measurements

Use useLayoutEffect sparingly because it can delay painting.


Re-rendering

Every Hook connects to React's rendering model.

State Change

↓

Component Executes Again

↓

Hooks Run Again

↓

New JSX

↓

DOM Updated

React remembers Hook state internally.


Choosing the Right Hook

Need UI State?

↓

useState

----------------

Complex State?

↓

useReducer

----------------

Need Shared Data?

↓

useContext

----------------

Need Side Effect?

↓

useEffect

----------------

Need DOM Access?

↓

useRef

----------------

Expensive Calculation?

↓

useMemo

----------------

Need Layout Measurement?

↓

useLayoutEffect

Custom Hooks

What are they?

A Custom Hook is simply a JavaScript function that uses other Hooks.

function useCounter() {

}

Always start with use.


Why?

To reuse stateful logic.

Instead of copying code,

extract it.


Example 1 — useCounter

function useCounter(initial=0){

 const [count,setCount]=useState(initial);

 const increment=()=>{
   setCount(c=>c+1);
 };

 const decrement=()=>{
   setCount(c=>c-1);
 };

 const reset=()=>{
   setCount(initial);
 };

 return {
   count,
   increment,
   decrement,
   reset
 };
}

Usage

const {
 count,
 increment
}=useCounter();

Example 2 — useToggle

function useToggle(initial=false){

 const [value,setValue]=
 useState(initial);

 const toggle=()=>{
   setValue(v=>!v);
 };

 return [value,toggle];
}

Usage

const [dark,toggle]=
useToggle();

Example 3 — useLocalStorage

function useLocalStorage(key,value){

 const [state,setState]=
 useState(()=>{
   return JSON.parse(
    localStorage.getItem(key)
   ) || value;
 });

 useEffect(()=>{

   localStorage.setItem(
     key,
     JSON.stringify(state)
   );

 },[key,state]);

 return [state,setState];
}

Example 4 — useFetch

function useFetch(url){

 const [data,setData]=
 useState(null);

 const [loading,setLoading]=
 useState(true);

 useEffect(()=>{

   fetch(url)
   .then(r=>r.json())
   .then(d=>{
      setData(d);
      setLoading(false);
   });

 },[url]);

 return {data,loading};
}

Usage

const {
 data,
 loading
}=useFetch("/api/users");

Example 5 — usePrevious

function usePrevious(value){

 const ref=useRef();

 useEffect(()=>{
   ref.current=value;
 });

 return ref.current;
}

Useful for comparing previous and current values.


When Should You Create a Custom Hook?

Create one when:

  • Logic repeats

  • Multiple components need the same behaviour

  • Components become too large

  • State + effects belong together

Don't create custom hooks for one-time logic.


Best Practices

  • Keep state local whenever possible.

  • Use Context only for shared application state.

  • Avoid unnecessary effects.

  • Don't overuse useMemo.

  • Use useReducer for complex state transitions.

  • Use useRef for mutable values that shouldn't trigger renders.

  • Prefer readable code over premature optimisation.

  • Extract reusable logic into custom hooks.


Interview Questions

Why were Hooks introduced?

To allow function components to use state and side effects while making logic easier to reuse and maintain.

Difference between useState and useReducer?

useState is ideal for simple state, while useReducer is better for complex state with multiple transitions.

When should you use useEffect?

Whenever you need to synchronise your component with an external system such as an API, timer, storage, or browser event.

What is useRef used for?

To persist mutable values across renders or access DOM elements without causing re-renders.

useMemo vs useRef?

  • useMemo caches computed values.

  • useRef stores mutable values or DOM references.

useEffect vs useLayoutEffect?

  • useEffect runs after the browser paints.

  • useLayoutEffect runs before painting, making it suitable for DOM measurements.

What is a Custom Hook?

A reusable function that combines React Hooks to encapsulate stateful logic.


Final Mental Model

User Action
      │
      ▼
State Update (useState/useReducer)
      │
      ▼
React Re-renders Component
      │
      ▼
useMemo Reuses Expensive Values
      │
      ▼
JSX Generated
      │
      ▼
DOM Updated
      │
      ├────────► useLayoutEffect (Before Paint)
      │
      ▼
Browser Paint
      │
      └────────► useEffect (After Paint)
      
Shared State ─────► useContext

DOM Access ───────► useRef

Reusable Logic ───► Custom Hooks

Key Takeaways

  • useState manages simple component state.

  • useEffect synchronises React with external systems.

  • useContext shares data without prop drilling.

  • useReducer manages complex state transitions.

  • useRef stores mutable values and DOM references without re-rendering.

  • useMemo avoids repeating expensive calculations.

  • useLayoutEffect runs before the browser paints and is mainly used for layout measurements.

  • Custom Hooks let you extract and reuse stateful logic across components.

  • Choose Hooks based on the problem you're solving, not because they're available.

  • Think of Hooks as tools that manage state, effects, shared data, performance, and logic reuse.