DEV Community

Lucas M Dev
Lucas M Dev

Posted on

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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

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

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>;
}
Enter fullscreen mode Exit fullscreen mode

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} />;
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)