Skillversitas

useCallback & useMemo

Why Is Memoization Important?

React re-renders child components every time the parent re-renders. Memoization prevents unnecessary re-renders by caching function results or values.

useCallback to Memoize Functions

useCallback returns a memoized function. It is useful when a function is passed as a prop.

import React, { useState, useCallback } from 'react';

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

  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []); // The function is not recreated

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <input value={text} onChange={e => setText(e.target.value)} />
      <MemoizedChild onClick={handleClick} />
    </div>
  );
}

const MemoizedChild = React.memo(({ onClick }) => {
  console.log('Child render');
  return <button onClick={onClick}>Child Button</button>;
});

useMemo to Memoize Values

useMemo returns a memoized value. It is useful for expensive computations.

function TodoList({ todos, filter }) {
  const visibleTodos = useMemo(() => {
    return todos.filter(todo => {
      if (filter === 'active') return !todo.completed;
      if (filter === 'completed') return todo.completed;
      return true;
    });
  }, [todos, filter]); // Only recomputes when dependencies change

  return <List items={visibleTodos} />;
}

Dependency Array

// The function is only recreated when a or b changes
const memoizedFn = useCallback(() => {
  return a + b;
}, [a, b]);

// The value is only recomputed when items changes
const total = useMemo(() => {
  return items.reduce((sum, item) => sum + item.price, 0);
}, [items]);

When to Use Them?

Use When
useCallback Function is passed to a memoized child
useMemo Expensive computation or a new object/array
Not needed Simple components, rendering is not expensive

Example: Performance Optimization

function ExpensiveList({ items, onSelect }) {
  const sortedItems = useMemo(() => {
    return [...items].sort((a, b) => a.name.localeCompare(b.name));
  }, [items]);

  const handleSelect = useCallback((id) => {
    onSelect(id);
  }, [onSelect]);

  return (
    <ul>
      {sortedItems.map(item => (
        <ExpensiveItem
          key={item.id}
          item={item}
          onSelect={handleSelect}
        />
      ))}
    </ul>
  );
}

const ExpensiveItem = React.memo(({ item, onSelect }) => {
  // Only re-renders when item or onSelect changes
  return (
    <li onClick={() => onSelect(item.id)}>
      {item.name} - {item.price}
    </li>
  );
});

Common Mistakes

// Wrong: incomplete dependency array
const memoizedFn = useCallback(() => {
  doSomething(a, b);
}, [a]); // Forgot b

// Correct
const memoizedFn = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

useCallback and useMemo help optimize performance. Next we will study useReducer for more complex state management.