Articles

useMemo vs useCallback: React Performance Optimization

useMemo vs useCallback: React Performance Optimization

React's optimization hooks, useMemo and useCallback, are powerful tools for improving application performance. While they serve similar purposes—preventing unnecessary recalculations and re-renders—they're designed for different use cases.

Core Differences

useMemo:

useCallback:

Understanding useMemo

The useMemo hook memoizes the result of a computation, preventing expensive calculations from running on every render.

import { useMemo } from "react";

function ExpensiveComponent({ data, filter }) {
  // This expensive computation will only re-run when data or filter changes
  const filteredData = useMemo(() => {
    console.log("Filtering data...");
    return data.filter((item) => item.name.includes(filter));
  }, [data, filter]);

  return (
    <div>
      <h2>Filtered Results: {filteredData.length}</h2>
      {filteredData.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}

When to Use useMemo:

  1. Expensive calculations: When computing a value requires significant CPU work
  2. Complex data transformations: When filtering, sorting, or mapping large arrays
  3. Preventing recreations of reference types: When you need to maintain referential equality for objects or arrays

Understanding useCallback

The useCallback hook memoizes a function definition, preventing the function from being recreated on every render.

import { useCallback } from "react";

function ParentComponent({ id }) {
  // This function reference remains the same between renders
  // as long as id doesn't change
  const handleClick = useCallback(() => {
    console.log("Clicked item with id:", id);
    // Perform some action with id
  }, [id]);

  return (
    <div>
      <h2>Parent Component</h2>
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

// Child component using React.memo to prevent unnecessary re-renders
const ChildComponent = React.memo(function ChildComponent({ onClick }) {
  console.log("Child component rendered");
  return <button onClick={onClick}>Click Me</button>;
});

When to Use useCallback:

  1. Event handlers passed to optimized child components: When the function is a prop for a memoized component
  2. Functions as dependencies: When the function is used in dependency arrays of other hooks
  3. Functions passed to custom hooks: To maintain referential equality between renders

Practical Use Case Comparison

Using useMemo

// Bad - recalculates on every render
function ProductList({ products, category }) {
  const filteredProducts = products.filter(
    (product) => product.category === category
  );

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

// Good - only recalculates when products or category changes
function ProductList({ products, category }) {
  const filteredProducts = useMemo(
    () => products.filter((product) => product.category === category),
    [products, category]
  );

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

Using useCallback

// Bad - creates a new function on every render
function SearchComponent({ onSearch }) {
  const handleSearchChange = (e) => {
    onSearch(e.target.value);
  };

  return <input onChange={handleSearchChange} />;
}

// Good - function reference stays the same across renders
function SearchComponent({ onSearch }) {
  const handleSearchChange = useCallback(
    (e) => {
      onSearch(e.target.value);
    },
    [onSearch]
  );

  return <input onChange={handleSearchChange} />;
}

Performance Considerations

Both hooks exist to optimize performance, but incorrect usage can actually harm performance:

Rules of Thumb

  1. Start without optimization: Add useMemo or useCallback only when you've identified a performance issue
  2. Measure the impact: Use React DevTools Profiler to verify performance improvements
  3. Don't memoize simple calculations: For simple operations, the overhead of memoization may exceed the benefits
  4. Always use with React.memo: useCallback provides the most benefit when paired with memoized child components

When Neither is Needed

Don't use these hooks for:

Conclusion

While useMemo and useCallback may seem similar at first glance, they serve distinct purposes:

Understanding when and how to apply these hooks will help you build more performant React applications without sacrificing code readability or maintainability.

Remember: optimization should be purposeful, not habitual. Always identify actual performance bottlenecks before applying these techniques.