React Hooks Explained: A Visual Guide for 2026

Published: (March 28, 2026 at 06:35 PM EDT)
4 min read
Source: Dev.to

Source: Dev.to

useState

import { useState } from 'react';

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

  return (
     setCount(count + 1)}>
      Clicked {count} times
    
  );
}

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

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 {user?.name};
}

Dependency array

DependencyBehavior
[]Run once on mount
[value]Run when value changes
noneRun after every render (usually wrong)

Cleanup

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

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

useContext

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

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

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

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

useRef

import { useRef } from 'react';

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

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

  return (
    <>
      
      Focus
    
  );
}

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

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

useMemo

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

  return (
    
{filtered.map(p => - {p.name}
)}

  );
}

When to use: Only for genuinely expensive calculations that you’ve measured cause performance issues.

useCallback

import { useCallback } from 'react';

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

  return ;
}

When to use: When passing callbacks to React.memo‑wrapped children to avoid unnecessary re‑renders.

useReducer

import { useReducer } from 'react';

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 (
    
      Count: {state.count}
       dispatch({ type: 'increment' })}>+
       dispatch({ type: 'decrement' })}>-
    
  );
}

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

Custom Hooks (Composability)

// 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 };
}

// Using the custom hook
function UserList() {
  const { data, loading, error } = useApi('/api/users');

  if (loading) return ;
  if (error) return ;
  return 
{data.map(u => - {u.name}
)}
;
}

Hook Overview

HookPurpose
useStateLocal state
useEffectSide effects, data fetching
useContextShare state without props
useRefDOM refs, mutable values
useMemoMemoize expensive calculations
useCallbackStable callback references
useReducerComplex state logic
0 views
Back to Blog

Related posts

Read more »