React Hooks Handbook
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
thiskeyword confusionLogic 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 valuesetCount()→ updates state0→ 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
useReducerfor complex state transitions.Use
useReffor 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?
useMemocaches computed values.useRefstores mutable values or DOM references.
useEffect vs useLayoutEffect?
useEffectruns after the browser paints.useLayoutEffectruns 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
useStatemanages simple component state.useEffectsynchronises React with external systems.useContextshares data without prop drilling.useReducermanages complex state transitions.useRefstores mutable values and DOM references without re-rendering.useMemoavoids repeating expensive calculations.useLayoutEffectruns 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.