React Hooks Complete Guide Explained with Examples
What Are React Hooks?
React Hooks are special built-in functions that let you use React features — like state, lifecycle methods, and context — directly inside functional components, without ever writing a class.
📅 A bit of history
Before React 16.8 (February 2019), managing state or lifecycle logic required Class Components — which came with a lot of boilerplate: constructor, this, bind(), and more. Hooks solved all of that cleanly.
The same counter component — once with a Class, and once with Hooks. The difference speaks for itself:
❌ Class Component (Old Way)
class Counter extends Component { constructor(props) { super(props); this.state = { count: 0 }; this.increment = this.increment.bind(this); } increment() { this.setState({ count: this.state.count + 1 }); } render() { return ( <button onClick={this.increment}> {this.state.count} </button> ); } }
✅ With Hooks (New Way)
import { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)} > {count} </button> ); } // Clean, readable, zero boilerplate.
Why Use Hooks?
Hooks fundamentally changed how React developers write components. Here are the solid reasons why the entire ecosystem moved to hooks:
Less Boilerplate
No more constructor, this.state, or bind(this). Your component is just a plain function.
Reusable Logic
Extract stateful logic into Custom Hooks and share it across many components without restructuring your component tree.
Easier to Test
Pure functions are far simpler to unit test than class instances with complex lifecycle methods.
Better Performance
useMemo and useCallback give you fine-grained control over re-renders.
No More "this" Confusion
JavaScript's this is notoriously tricky. Hooks eliminate the problem — there is no this in functional components.
Full Ecosystem Support
Redux, React Router, React Query — every major library now ships a hooks-based API. It is the standard.
Types of Hooks
React ships with 10 built-in hooks, grouped into five categories:
State Hooks
useState, useReducer
Effect Hooks
useEffect, useLayoutEffect
Context Hook
useContext
Ref Hooks
useRef, useImperativeHandle
Performance Hooks
useMemo, useCallback
Debug Hook
useDebugValue
Rules of Hooks
React enforces two strict rules. Breaking either one causes subtle, hard-to-debug issues:
Rule 1 — Only call hooks at the top level
Never call a hook inside an if statement, a for loop, or a nested function. React relies on the order hooks are called to preserve state correctly between renders.
Rule 2 — Only call hooks from React functions
Hooks can only be called inside React functional components or Custom Hooks — never inside a plain JavaScript function.
// ❌ WRONG — hook called inside an if block function BadComponent() { if (someCondition) { const [val, setVal] = useState(0); // Rule violation! } } // ✅ CORRECT — always call at the top level function GoodComponent() { const [val, setVal] = useState(0); // Always at top if (someCondition) { setVal(42); // Using state here is perfectly fine } }
1. useState
The most used hook. It adds a piece of state to a functional component. Whenever the state value changes, React re-renders the component with the updated value.
Array destructuring returns two things: the current value and a setter function. Call the setter to update the value and trigger a re-render.
import { useState } from 'react'; function LoginForm() { // string state const [name, setName] = useState(''); // number state const [age, setAge] = useState(0); // boolean state const [isLoggedIn, setLoggedIn] = useState(false); const handleLogin = () => { if (name && age > 0) setLoggedIn(true); }; if (isLoggedIn) { return <h2>Welcome, {name}! 🎉</h2>; } return ( <div> <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Your name" /> <input type="number" value={age} onChange={(e) => setAge(Number(e.target.value))} placeholder="Your age" /> <button onClick={handleLogin}>Login</button> </div> ); }
Pro tip: Never mutate state directly (e.g. state.name = "John"). Always use the setter function — React detects changes by comparing references, not deep equality.
2. useEffect
The side-effect manager. Use it whenever you need to interact with something outside React — API calls, event listeners, timers, or direct DOM manipulation.
// side-effect code here
return () => { /* optional cleanup */ };
}, [dependencies]);
[]
Run once
Only on component mount
[dep]
On dep change
Runs whenever dep updates
omitted
Every render
Avoid — causes infinite loops
import { useState, useEffect } from 'react'; function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); // Re-fetch whenever userId changes useEffect(() => { setLoading(true); fetch(`https://api.example.com/users/${userId}`) .then((res) => res.json()) .then((data) => { setUser(data); setLoading(false); }); // Cleanup runs before the next effect or on unmount return () => console.log('Cleanup previous fetch'); }, [userId]); if (loading) return <p>Loading...</p>; return ( <div> <h2>{user.name}</h2> <p>{user.email}</p> </div> ); }
3. useContext
The global data hook. Use it to share data — like a logged-in user, theme, or language preference — across many components without manually passing props at every level.
What is prop drilling?
Passing a prop through several intermediate components that do not actually need it, just to reach a deeply nested child. It makes code tightly coupled and painful to refactor. useContext eliminates this entirely.
import { createContext, useContext, useState } from 'react'; // Step 1 — Create the context const ThemeContext = createContext(null); // Step 2 — Build a Provider component function ThemeProvider({ children }) { const [theme, setTheme] = useState('light'); const toggle = () => setTheme((t) => (t === 'light' ? 'dark' : 'light')); return ( <ThemeContext.Provider value={{ theme, toggle }}> {children} </ThemeContext.Provider> ); } // Step 3 — Consume anywhere in the tree, no prop drilling function Navbar() { const { theme, toggle } = useContext(ThemeContext); return ( <nav className={theme === 'dark' ? 'bg-black' : 'bg-white'}> <span>Theme: {theme}</span> <button onClick={toggle}>Toggle 🌓</button> </nav> ); } function App() { return ( <ThemeProvider> <Navbar /> </ThemeProvider> ); }
4. useReducer
The big sibling of useState. When your state has multiple sub-values, or the next state depends on the previous one in complex ways, useReducer gives you a cleaner, more predictable structure — inspired by Redux.
import { useReducer, useState } from 'react'; // Pure function — takes state + action, returns new state function todoReducer(state, action) { switch (action.type) { case 'ADD': return [...state, { id: Date.now(), text: action.text, done: false }]; case 'TOGGLE': return state.map((t) => t.id === action.id ? { ...t, done: !t.done } : t ); case 'DELETE': return state.filter((t) => t.id !== action.id); default: return state; } } function TodoApp() { const [todos, dispatch] = useReducer(todoReducer, []); const [input, setInput] = useState(''); const add = () => { if (!input.trim()) return; dispatch({ type: 'ADD', text: input }); setInput(''); }; return ( <div> <input value={input} onChange={(e) => setInput(e.target.value)} /> <button onClick={add}>Add</button> {todos.map((todo) => ( <div key={todo.id}> <span className={todo.done ? 'line-through' : ''} onClick={() => dispatch({ type: 'TOGGLE', id: todo.id })} > {todo.text} </span> <button onClick={() => dispatch({ type: 'DELETE', id: todo.id })}>🗑️</button> </div> ))} </div> ); }
5. useCallback
Memoizes a function reference. Every render creates a new function instance. When you pass that function as a prop to a memoized child, the child re-renders unnecessarily — even if nothing actually changed. useCallback solves this by returning the same function reference until its dependencies change.
useCallback vs useMemo: useCallback(fn, deps) memoizes the function itself. useMemo(() => value, deps) memoizes the computed return value. Same mechanism, different output.
import { useState, useCallback, memo } from 'react'; // Child wrapped in memo — skips re-render if props unchanged const Button = memo(({ onClick, label }) => { console.log(`Rendering: ${label}`); return <button onClick={onClick}>{label}</button>; }); function Parent() { const [count, setCount] = useState(0); const [other, setOther] = useState(0); // Stable reference — Button will NOT re-render when 'other' changes const increment = useCallback(() => { setCount((c) => c + 1); }, []); return ( <div> <p>Count: {count} | Other: {other}</p> <Button onClick={increment} label="Increment Count" /> <button onClick={() => setOther((o) => o + 1)}>Other++</button> </div> ); }
6. useMemo
Memoizes a computed value. If you have an expensive calculation — filtering a large list, heavy number crunching — useMemo caches the result and only recalculates when its dependencies change, not on every render.
import { useState, useMemo } from 'react'; // Intentionally slow function function findPrimes(limit) { console.log('Recalculating primes...'); const primes = []; for (let i = 2; i <= limit; i++) { let isPrime = true; for (let j = 2; j < i; j++) { if (i % j === 0) { isPrime = false; break; } } if (isPrime) primes.push(i); } return primes; } function PrimeCalculator() { const [limit, setLimit] = useState(100); const [darkMode, setDark] = useState(false); // Only recalculates when 'limit' changes // Toggling darkMode does NOT trigger findPrimes again const primes = useMemo(() => findPrimes(limit), [limit]); return ( <div> <input type="number" value={limit} onChange={(e) => setLimit(Number(e.target.value))} /> <p>Primes found: {primes.length}</p> <button onClick={() => setDark((d) => !d)}> Toggle Dark Mode (no recalculation) </button> </div> ); }
7. useRef
Two jobs, one hook. First: gives you a direct reference to a DOM element (focus an input, measure size, play media). Second: stores a mutable value that persists across renders without triggering a re-render when it changes.
import { useState, useRef, useEffect } from 'react'; function SmartForm() { const [name, setName] = useState(''); // Job 1 — DOM reference const inputRef = useRef(null); // Job 2 — mutable value, no re-render on change const renderCount = useRef(0); useEffect(() => { inputRef.current.focus(); // Auto-focus on mount }, []); useEffect(() => { renderCount.current += 1; // Silent counter — no re-render }); return ( <div> <input ref={inputRef} value={name} onChange={(e) => setName(e.target.value)} placeholder="Auto-focused input" /> <p>Render count: {renderCount.current}</p> </div> ); }
8. useImperativeHandle
Controls what the parent can access via ref. Normally a parent ref gives full access to the child's DOM node. This hook lets you expose only a specific, curated set of methods — keeping internal DOM details private. Always paired with forwardRef.
import { useRef, useImperativeHandle, forwardRef } from 'react'; // Child — only exposes focus() and clear() to the parent const FancyInput = forwardRef((props, ref) => { const inputRef = useRef(null); useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus(), clear: () => { inputRef.current.value = ''; }, // inputRef.current is NOT accessible to the parent })); return <input ref={inputRef} placeholder="Controlled externally" />; }); // Parent — can only call focus() and clear() function Parent() { const ref = useRef(null); return ( <div> <FancyInput ref={ref} /> <button onClick={() => ref.current.focus()}>Focus</button> <button onClick={() => ref.current.clear()}>Clear</button> </div> ); }
9. useLayoutEffect
Like useEffect, but fires before the browser paints. Use it when you need to read DOM layout measurements and make immediate changes that must be visible on the very first paint — preventing a visible flicker.
useEffect timing
Render → Browser paints → Effect fires
User may briefly see the old layout
useLayoutEffect timing
Render → Effect fires → Browser paints
No flicker — user only sees the final layout
import { useState, useRef, useLayoutEffect } from 'react'; function Tooltip({ text, children }) { const tooltipRef = useRef(null); const [pos, setPos] = useState({ top: 0, left: 0 }); // Read DOM measurements BEFORE the browser paints useLayoutEffect(() => { const { height } = tooltipRef.current.getBoundingClientRect(); setPos({ top: -height - 8, left: 0 }); }, []); return ( <div className="relative inline-block"> {children} <div ref={tooltipRef} className="absolute bg-black text-white text-xs px-2 py-1 rounded" style={{ top: pos.top, left: pos.left }} > {text} </div> </div> ); } // Rule of thumb: use useEffect by default. // Only switch to useLayoutEffect if you see a flicker.
10. useDebugValue
A developer tool, not a UI feature. Displays a custom label in React DevTools next to your Custom Hook — making it easier to inspect what it is doing during development. It has zero effect on the running application.
import { useState, useEffect, useDebugValue } from 'react'; function useOnlineStatus() { const [isOnline, setIsOnline] = useState(navigator.onLine); useEffect(() => { const on = () => setIsOnline(true); const off = () => setIsOnline(false); window.addEventListener('online', on); window.addEventListener('offline', off); return () => { window.removeEventListener('online', on); window.removeEventListener('offline', off); }; }, []); // DevTools shows: "Online 🟢" or "Offline 🔴" useDebugValue(isOnline ? 'Online 🟢' : 'Offline 🔴'); return isOnline; } function StatusBar() { const isOnline = useOnlineStatus(); return <p>{isOnline ? 'Connected ✅' : 'No Internet ❌'}</p>; }
Custom Hooks — Build Your Own
Custom Hooks are one of the most powerful patterns in React. They let you extract stateful logic out of a component into a reusable function — keeping components lean and logic shareable across the entire codebase.
The one rule: A Custom Hook's name must start with use (e.g. useFetch, useLocalStorage). React uses this convention to enforce the Rules of Hooks and to label it correctly in DevTools.
import { useState, useEffect } from 'react'; // Reusable data-fetching hook function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { setLoading(true); setError(null); fetch(url) .then((res) => { if (!res.ok) throw new Error(`HTTP error: ${res.status}`); return res.json(); }) .then((d) => { setData(d); setLoading(false); }) .catch((e) => { setError(e.message); setLoading(false); }); }, [url]); return { data, loading, error }; } // Use it in any component — one line of code function UserList() { const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users'); if (loading) return <p>Loading users...</p>; if (error) return <p>Error: {error}</p>; return ( <ul> {data.map((user) => ( <li key={user.id}>{user.name} — {user.email}</li> ))} </ul> ); }
Quick Reference
All 10 hooks at a glance:
| Hook | When to Use | Type |
|---|---|---|
| useState | Store a value that triggers a re-render on change | State |
| useReducer | Manage complex state with multiple actions | State |
| useEffect | Run side effects — API calls, subscriptions, timers | Effect |
| useLayoutEffect | Read DOM layout before the browser paints | Effect |
| useContext | Consume shared global data without prop drilling | Context |
| useRef | Reference a DOM node or persist a value without re-render | Ref |
| useImperativeHandle | Expose specific child methods to a parent via ref | Ref |
| useCallback | Memoize a function to prevent unnecessary child re-renders | Perf |
| useMemo | Cache an expensive computed value | Perf |
| useDebugValue | Add a label to a Custom Hook in React DevTools | Debug |
🎉 Wrapping Up
You have now covered all 10 built-in React Hooks. Do not worry about memorising every single one — that is not how it works in practice. Start with the essentials and let the rest come naturally as you build real projects.
A practical learning path:
Beginner
useState + useEffect
Covers 80% of real use cases
Intermediate
+ useContext, useRef, useReducer
Scale your app architecture
Advanced
+ useMemo, useCallback, Custom Hooks
Optimize and abstract your logic
The best way to master hooks is to build things, break them, and fix them. Happy coding! 🚀
More In React js
Explore more React js articles
Read more articles from the same category or open the full category archive directly.
React js
Core React Interview Questions Every Developer Should Know
General Knowledge
Bharat Ratna Award Winners List 1954 to 2024: History, Facts and Full List
General Knowledge
Nobel Prize 2025 Winners, History, Categories, Indian Winners & Important Facts (Hindi + English)
Recent Posts
Bharat Ratna Award Winners List 1954 to 2024: History, Facts and Full List
26 March 2026
Nobel Prize 2025 Winners, History, Categories, Indian Winners & Important Facts (Hindi + English)
26 March 2026
JavaScript ES6+ Interview Questions & Concepts (Destructuring, Spread, Rest)
21 March 2026
JavaScript Basics Explained: Variables, Data Types & Functions with Examples
21 March 2026
JavaScript Arrays Explained with Examples Part 2 (Complete Beginner Guide)
20 March 2026
JavaScript Arrays Explained with Examples (Complete Beginner Guide)
20 March 2026
Callback vs Promises in JavaScript
20 March 2026
Async/Await Error Handling in JavaScript
20 March 2026
Javascript Array Methods Reduce Filter Includes Find findindex Sort
17 March 2026
JavaScript Array Methods Explained with Examples
17 March 2026