React Hooks Explained: A Visual Guide for 2026

React Hooks can be confusing when you’re new to them. This guide explains the most important ones with clear examples.

useState — Local component state

import { useState } from 'react';

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

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

When to use: Anything the component needs to remember between renders — form values, toggles, counters.

Gotcha: State updates are asynchronous.

// ❌ This won't work as expected
setCount(count + 1);
setCount(count + 1); // Both use the same `count` value

// ✅ Use the updater function instead
setCount(prev => prev + 1);
setCount(prev => prev + 1); // Now it's +2

useEffect — Side effects

import { useEffect, useState } from 'react';

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

  useEffect(() => {
    // Runs after every render where userId changes
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(setUser);
  }, [userId]); // dependency array

  return <div>{user?.name}</div>;
}

The dependency array:

  • [] — Run once on mount
  • [value] — Run when value changes
  • No array — Run after every render (usually wrong!)

Always clean up subscriptions:

useEffect(() => {
  const subscription = subscribe(userId);

  return () => {
    subscription.unsubscribe(); // cleanup!
  };
}, [userId]);

useContext — Share state without prop drilling

// 1. Create the context
const ThemeContext = createContext('light');

// 2. Provide it at the top
function App() {
  const [theme, setTheme] = useState('dark');
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Page />
    </ThemeContext.Provider>
  );
}

// 3. Use it anywhere in the tree
function Button() {
  const { theme } = useContext(ThemeContext);
  return <button className={theme}>Click me</button>;
}

useRef — DOM references and mutable values

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

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus</button>
    </>
  );
}

Also useful for: storing values that shouldn’t trigger re-renders.

const renderCount = useRef(0);
renderCount.current += 1; // Doesn't cause re-render

useMemo — Expensive calculations

function ProductList({ products, minPrice }) {
  // This only recalculates when products or minPrice changes
  const filtered = useMemo(() => {
    return products.filter(p => p.price >= minPrice);
  }, [products, minPrice]);

  return <ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}

Don’t overuse this. Only use it when:

  • The calculation is genuinely expensive
  • You’ve measured it’s causing performance issues

useCallback — Stable function references

function Parent({ id }) {
  // Without useCallback, a new function is created every render
  const handleClick = useCallback(() => {
    doSomethingWith(id);
  }, [id]);

  return <ExpensiveChild onClick={handleClick} />;
}

When to use: When passing callbacks to React.memo wrapped children.

useReducer — Complex state logic

const initialState = { count: 0, error: null, loading: false };

function reducer(state, action) {
  switch (action.type) {
    case 'increment': return { ...state, count: state.count + 1 };
    case 'decrement': return { ...state, count: state.count - 1 };
    case 'reset': return initialState;
    default: return state;
  }
}

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

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

Use instead of useState when: multiple pieces of state are related and change together.

Custom Hooks — Reuse logic

The real power of hooks is composability. Extract logic into custom hooks:

// Custom hook for API calls
function useApi(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then(r => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.json();
      })
      .then(data => { setData(data); setLoading(false); })
      .catch(err => { setError(err.message); setLoading(false); });
  }, [url]);

  return { data, loading, error };
}

// Use it
function UserList() {
  const { data, loading, error } = useApi('/api/users');

  if (loading) return <Spinner />;
  if (error) return <Error message={error} />;
  return <ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

Quick reference

Hook Purpose
useState Local state
useEffect Side effects, data fetching
useContext Share state without props
useRef DOM refs, mutable values
useMemo Memoize expensive calcs
useCallback Stable callback references
useReducer Complex state logic

Building something with React? DevToolkit has free tools like a JSON formatter, UUID generator, and more — no signup needed.