Exambodh - Practice Aptitude, Reasoning & GK Questions

Back to Articles
React jsReact js

React Hooks Complete Guide Explained with Examples

React js·

Share Article

Share this article on social platforms or copy the direct link.

1

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)

Counter.jsx
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)

Counter.jsx
import { useState } from 'react';
 
function Counter() {
  const [count, setCount] =
    useState(0);
 
  return (
    <button
      onClick={() => setCount(count + 1)}
    >
      {count}
    </button>
  );
}
// Clean, readable, zero boilerplate.
2

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.

3

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

4

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.

rules-example.jsx
// ❌ 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
  }
}
State Hook

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.

const [state, setState] = useState(initialValue);

Array destructuring returns two things: the current value and a setter function. Call the setter to update the value and trigger a re-render.

LoginForm.jsx
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.

Effect Hook

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.

useEffect(() => {
  // 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

UserProfile.jsx
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>
  );
}
Context Hook

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.

ThemeContext.jsx
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>
  );
}
State Hook

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.

const [state, dispatch] = useReducer(reducerFn, initialState);
TodoApp.jsx
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>
  );
}
Performance Hook

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.

UseCallbackExample.jsx
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>
  );
}
Performance Hook

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.

PrimeCalculator.jsx
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>
  );
}
Ref Hook

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.

UseRefExample.jsx
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>
  );
}
Ref Hook

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.

FancyInput.jsx
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>
  );
}
Effect Hook

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

Tooltip.jsx
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.
Debug Hook

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.

useOnlineStatus.js
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.

useFetch.js
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.