useEffect vs useCallback vs useMemo: The Complete Guide

Omkar PatilOmkar Patil
6 min read

React hooks can be confusing, especially when you're trying to figure out which one to use when. Let's break down these three essential hooks with simple explanations, practical examples, and easy-to-remember rules.

๐ŸŽฏ Quick Reference Table

HookPurposeReturnsWhen to Use
useEffectHandle side effectsNothingAPI calls, subscriptions, DOM manipulation
useCallbackMemoize functionsMemoized functionPrevent unnecessary re-renders of child components
useMemoMemoize valuesMemoized valueExpensive calculations, complex object creation

๐Ÿ”„ useEffect: The Side Effect Manager

What it does: Handles side effects in your components (things that happen "on the side" of rendering).

Think of it as: Your component's event listener for lifecycle moments.

Basic Syntax

useEffect(() => {
  // Side effect code here
  return () => {
    // Cleanup code (optional)
  };
}, [dependencies]); // Dependencies array

When to Use useEffect

โœ… Perfect for:

  • API calls and data fetching

  • Setting up subscriptions (WebSocket, event listeners)

  • DOM manipulation (focus, scroll position)

  • Cleanup tasks (removing event listeners, canceling requests)

  • Running code after render (analytics, logging)

Example: Data Fetching

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

  useEffect(() => {
    const fetchUser = async () => {
      setLoading(true);
      try {
        const response = await fetch(`/api/users/${userId}`);
        const userData = await response.json();
        setUser(userData);
      } catch (error) {
        console.error('Failed to fetch user:', error);
      } finally {
        setLoading(false);
      }
    };

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

  if (loading) return <div>Loading...</div>;
  return <div>Hello, {user?.name}!</div>;
}

Dependency Array Rules

  • No array: Runs after every render

  • Empty array []: Runs once after initial render

  • With dependencies [value]: Runs when dependencies change

๐Ÿ”„ useCallback: The Function Memoizer

What it does: Returns a memoized version of a function that only changes when its dependencies change.

Think of it as: A way to keep the same function reference across re-renders.

Basic Syntax

const memoizedCallback = useCallback(() => {
  // Function code here
}, [dependencies]);

When to Use useCallback

โœ… Perfect for:

  • Passing functions to child components (prevents unnecessary re-renders)

  • Functions used in other hooks' dependencies

  • Event handlers in lists (optimization)

  • Expensive function creation

Example: Preventing Child Re-renders

function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [filter, setFilter] = useState('all');

  // Without useCallback, this function is recreated on every render
  // causing TodoList to re-render unnecessarily
  const addTodo = useCallback((text) => {
    setTodos(prev => [...prev, { id: Date.now(), text, completed: false }]);
  }, []); // No dependencies - function never changes

  const deleteTodo = useCallback((id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  }, []);

  return (
    <div>
      <TodoForm onAddTodo={addTodo} />
      <TodoList todos={todos} onDeleteTodo={deleteTodo} />
      <FilterButtons filter={filter} setFilter={setFilter} />
    </div>
  );
}

// Child component - only re-renders when todos actually change
const TodoList = React.memo(({ todos, onDeleteTodo }) => {
  console.log('TodoList rendered'); // This won't log unnecessarily

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
          <button onClick={() => onDeleteTodo(todo.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
});

๐Ÿ’พ useMemo: The Value Memoizer

What it does: Returns a memoized value that only recalculates when its dependencies change.

Think of it as: A way to cache expensive calculations.

Basic Syntax

const memoizedValue = useMemo(() => {
  return expensiveCalculation(a, b);
}, [a, b]);

When to Use useMemo

โœ… Perfect for:

  • Expensive calculations (complex math, data processing)

  • Filtering/sorting large lists

  • Creating complex objects (to maintain reference equality)

  • Derived state (values computed from other state)

Example: Expensive Calculation

function ProductList({ products, searchTerm, category }) {
  // Expensive filtering operation - only recalculate when inputs change
  const filteredProducts = useMemo(() => {
    console.log('Filtering products...'); // Only logs when dependencies change

    return products
      .filter(product => {
        const matchesSearch = product.name
          .toLowerCase()
          .includes(searchTerm.toLowerCase());
        const matchesCategory = category === 'all' || product.category === category;
        return matchesSearch && matchesCategory;
      })
      .sort((a, b) => a.name.localeCompare(b.name));
  }, [products, searchTerm, category]);

  // Complex object creation - prevents unnecessary re-renders
  const listConfig = useMemo(() => ({
    showImages: true,
    showPrices: true,
    layout: 'grid'
  }), []); // Empty dependency - object never changes

  return <ProductGrid products={filteredProducts} config={listConfig} />;
}

๐Ÿค” Common Confusion Points

useCallback vs useMemo

// These are equivalent:
const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

const memoizedCallback = useMemo(() => {
  return () => doSomething(a, b);
}, [a, b]);

// But useCallback is cleaner for functions

When NOT to Use These Hooks

โŒ Don't use useCallback/useMemo for:

  • Simple calculations (more overhead than benefit)

  • Primitive values (strings, numbers, booleans are cheap to compare)

  • Every function/value (premature optimization)

// โŒ Unnecessary - primitive values are cheap
const doubled = useMemo(() => count * 2, [count]);

// โœ… Better - just calculate directly
const doubled = count * 2;

๐ŸŽฏ Easy Memory Tricks

The "3 E's" Rule

  • useEffect: Effects (side effects, external operations)

  • useCallback: Event handlers (functions passed to children)

  • useMemo: Expensive operations (calculations, processing)

When in Doubt

  1. Need to do something after render? โ†’ useEffect

  2. Passing a function to a child component? โ†’ useCallback

  3. Have an expensive calculation? โ†’ useMemo

  4. Not sure if it's expensive? โ†’ Profile first, optimize later

๐Ÿ† Best Practices

1. Start Without Optimization

// Start simple
function Component({ items }) {
  const expensiveValue = calculateSomething(items);
  return <div>{expensiveValue}</div>;
}

// Add optimization only if needed
function Component({ items }) {
  const expensiveValue = useMemo(() => calculateSomething(items), [items]);
  return <div>{expensiveValue}</div>;
}

2. Use React.memo with useCallback

// Combine for maximum benefit
const ExpensiveChild = React.memo(({ onClick, data }) => {
  // This component only re-renders when props actually change
  return <div onClick={onClick}>{data}</div>;
});

function Parent() {
  const handleClick = useCallback(() => {
    // Handle click
  }, []);

  return <ExpensiveChild onClick={handleClick} data="static" />;
}

3. Profile Before Optimizing

Use React DevTools Profiler to identify actual performance bottlenecks before adding memoization.

๐Ÿš€ Summary

ScenarioUse This HookWhy
Fetch data when component mountsuseEffectSide effect after render
Subscribe to WebSocketuseEffectSide effect with cleanup
Pass function to child componentuseCallbackPrevent child re-renders
Filter large listuseMemoExpensive calculation
Create complex configuration objectuseMemoMaintain reference equality

Remember: Measure first, optimize second. These hooks are powerful tools, but they're not always necessary. Start with simple code and add optimization when you have a proven performance issue.

Happy coding! ๐ŸŽ‰

0
Subscribe to my newsletter

Read articles from Omkar Patil directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Omkar Patil
Omkar Patil