Published on

When and How to Use useMemo and useCallback: A Practical Guide

Authors
  • avatar
    Name
    Mohit Verma
    Twitter

Hey there! 👋 If you've been working with React, you've probably heard about useMemo and useCallback. But when should you actually use them? Let's break it down in simple terms with some real-world examples.

The Golden Rule First!

Before we dive in, remember this: Don't optimize prematurely! Both useMemo and useCallback are optimization hooks. You don't need them everywhere. In fact, incorrect usage can make your app slower.

When to Use useMemo

Use useMemo when you have:

  1. Expensive calculations
  2. Complex object creation that triggers re-renders
  3. Values used in dependency arrays of other hooks

Let's look at some examples:

// 🚫 Don't do this - simple calculation
const total = useMemo(() => price + tax, [price, tax]);

// ✅ Do this - expensive calculation
const expensiveValue = useMemo(() => {
  return someArray.reduce((acc, item) => {
    // Complex calculations here
    return acc + complexOperation(item);
  }, 0);
}, [someArray]);

// ✅ Do this - object used in deps
const memoizedObject = useMemo(() => ({
  id: props.id,
  name: props.name
}), [props.id, props.name]);

useEffect(() => {
  // This effect won't re-run unnecessarily
  doSomething();
}, [memoizedObject]);

Smart useCallback Usage

useCallback is your friend when:

  1. Passing callbacks to optimized child components
  2. Callbacks are used in dependency arrays
  3. Preserving function references is important

Here's how to use it effectively:

// ✅ Good use case - callback passed to optimized component
const MemoizedChild = memo(({ onClick }) => {
  console.log("Child rendered");
  return <button onClick={onClick}>Click me</button>;
});

function Parent() {
  const handleClick = useCallback(() => {
    console.log("Button clicked");
  }, []); // No dependencies needed here

  return <MemoizedChild onClick={handleClick} />;
}

// ✅ Good use case - callback in dependency array
function DataFetcher() {
  const [data, setData] = useState(null);
  
  const fetchData = useCallback(async () => {
    const response = await fetch('/api/data');
    const json = await response.json();
    setData(json);
  }, []); // Empty deps as it doesn't depend on props/state

  useEffect(() => {
    fetchData();
  }, [fetchData]); // Used in deps array

  return <div>{/* render data */}</div>;
}

Common Mistakes to Avoid

  1. Over-optimization
// 🚫 Don't do this
function SimpleComponent() {
  // Unnecessary memoization
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

  return <button onClick={handleClick}>Click me</button>;
}

// ✅ Do this instead
function SimpleComponent() {
  const handleClick = () => console.log('clicked');
  return <button onClick={handleClick}>Click me</button>;
}
  1. Missing Dependencies
// 🚫 Don't do this
const memoizedValue = useMemo(() => {
  return data.filter(item => item.id === selectedId);
}, []); // Missing dependencies!

// ✅ Do this instead
const memoizedValue = useMemo(() => {
  return data.filter(item => item.id === selectedId);
}, [data, selectedId]); // Include all dependencies

Real-World Examples

1. Data Grid with Sorting

function DataGrid({ data, sortConfig }) {
  // Memoize expensive sorting operation
  const sortedData = useMemo(() => {
    console.log('Sorting data...'); // You'll see this less often
    return [...data].sort((a, b) => {
      if (sortConfig.direction === 'asc') {
        return a[sortConfig.key] > b[sortConfig.key] ? 1 : -1;
      }
      return a[sortConfig.key] < b[sortConfig.key] ? 1 : -1;
    });
  }, [data, sortConfig.key, sortConfig.direction]);

  return (
    <table>
      <tbody>
        {sortedData.map(item => (
          <tr key={item.id}>{/* render row */}</tr>
        ))}
      </tbody>
    </table>
  );
}

2. Search Component with Debounce

function SearchComponent({ onSearch }) {
  // Memoize debounced function
  const debouncedSearch = useCallback(
    debounce((term) => {
      onSearch(term);
    }, 300),
    [onSearch]
  );

  return (
    <input
      type="text"
      onChange={e => debouncedSearch(e.target.value)}
      placeholder="Search..."
    />
  );
}

Performance Testing Tip

Want to know if your memoization is helping? Use React DevTools:

  1. Open React DevTools
  2. Go to Profiler
  3. Record interactions
  4. Look for unnecessary re-renders

When to Skip Memoization

Skip these hooks when:

  1. The computation is simple
  2. The component always needs to re-render anyway
  3. The props/state change frequently
// 🚫 Don't memoize simple calculations
const doubledValue = useMemo(() => number * 2, [number]);

// ✅ Just do the calculation
const doubledValue = number * 2;

Conclusion

Remember:

  1. Profile first, optimize later
  2. Use these hooks when you have measurable performance issues
  3. Always include proper dependencies
  4. Test performance impact before and after

Happy coding! And remember, the best optimization is often the one you don't need to make! 😉