Skip to content

React Hooks — Complete Guide (useState, useEffect, useContext, useReducer)

DodaTech 3 min read

In this tutorial, you'll learn about React Hooks. We cover key concepts, practical examples, and best practices.

What You'll Learn

Master all React hooks — useState for state, useEffect for side effects, useContext for global data, useReducer for complex state, and performance hooks.

Why It Matters

Hooks are the foundation of modern React. They replaced class components and make code cleaner, more reusable, and easier to test.

Real-World Use

Managing form state (useState), fetching API data (useEffect), sharing theme across components (useContext), or handling complex state like a shopping cart (useReducer).

useState — Local State

import { useState } from "react";

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>

      <input value={name} onChange={e => setName(e.target.value)} />
      <p>Name: {name}</p>
    </div>
  );
}

Rules:

  • Call useState at the top level of your component
  • Don't call hooks inside loops, conditions, or nested functions
  • State updates are asynchronous

useEffect — Side Effects

import { useState, useEffect } from "react";

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchUser() {
      setLoading(true);
      const response = await fetch(
        `https://api.example.com/users/${userId}`
      );
      const data = await response.json();
      setUser(data);
      setLoading(false);
    }

    fetchUser();
  }, [userId]);  // Re-run when userId changes

  if (loading) return <p>Loading...</p>;
  return <h1>{user.name}</h1>;
}

Dependency array patterns:

  • useEffect(fn) — runs after every render
  • useEffect(fn, []) — runs once on mount
  • useEffect(fn, [x, y]) — runs when x or y change

useContext — Global State

import { createContext, useContext, useState } from "react";

// Step 1: Create context
const ThemeContext = createContext("light");

// Step 2: Provide value
function App() {
  const [theme, setTheme] = useState("light");

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// Step 3: Consume anywhere
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button
      onClick={() =>
        setTheme(theme === "light" ? "dark" : "light")
      }
      style={{
        background: theme === "dark" ? "#333" : "#fff",
        color: theme === "dark" ? "#fff" : "#333",
      }}
    >
      Toggle Theme (current: {theme})
    </button>
  );
}

useReducer — Complex State

import { useReducer } from "react";

// Reducer: (state, action) => newState
function cartReducer(state, action) {
  switch (action.type) {
    case "ADD_ITEM":
      return [...state, action.item];
    case "REMOVE_ITEM":
      return state.filter(item => item.id !== action.id);
    case "CLEAR":
      return [];
    case "UPDATE_QUANTITY":
      return state.map(item =>
        item.id === action.id
          ? { ...item, quantity: action.quantity }
          : item
      );
    default:
      return state;
  }
}

function ShoppingCart() {
  const [cart, dispatch] = useReducer(cartReducer, []);

  function addItem() {
    dispatch({
      type: "ADD_ITEM",
      item: {
        id: Date.now(),
        name: "New Item",
        quantity: 1,
        price: 9.99,
      },
    });
  }

  return (
    <div>
      <button onClick={addItem}>Add Item</button>
      <button onClick={() => dispatch({ type: "CLEAR" })}>
        Clear Cart
      </button>
      <ul>
        {cart.map(item => (
          <li key={item.id}>
            {item.name} × {item.quantity}
            <button
              onClick={() =>
                dispatch({
                  type: "REMOVE_ITEM",
                  id: item.id,
                })
              }
            >
              Remove
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

useRef — Mutable References

import { useRef } from "react";

function AutoFocus() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleClick}>Focus Input</button>
    </div>
  );
}

Use cases: DOM access, storing interval IDs, keeping mutable values that don't trigger re-render.

useMemo — Memoized Values

import { useMemo } from "react";

function ExpensiveCalculation({ data }) {
  const sortedData = useMemo(() => {
    return [...data].sort((a, b) => b.score - a.score);
  }, [data]);

  return (
    <ul>
      {sortedData.map(item => (
        <li key={item.id}>{item.name}: {item.score}</li>
      ))}
    </ul>
  );
}

useCallback — Memoized Functions

import { useCallback } from "react";

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

  // Only re-created if count changes
  const increment = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);

  return <Child onIncrement={increment} />;
}

Hooks Quick Reference

Hook Purpose
useState Local component state
useEffect Side effects (fetch, subscriptions)
useContext Read from context
useReducer Complex state logic
useRef Mutable references, DOM access
useMemo Memoize computed values
useCallback Memoize function references
useImperativeHandle Customize ref expose
useLayoutEffect Synchronous DOM mutations
useDebugValue Custom hook debug labels

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro