3 min read By Emily Watson

Advanced React Hooks: useCallback and useMemo

Master React's performance optimization hooks. Learn when and how to use useCallback and useMemo effectively in your applications.

React JavaScript Performance Hooks
Advanced React Hooks: useCallback and useMemo

Understanding React Performance Optimization

React’s useCallback and useMemo hooks are powerful tools for optimizing component performance. However, they’re often misunderstood and misused. Let’s dive deep into when and how to use them effectively.

useCallback: Memoizing Functions

The useCallback hook returns a memoized version of the callback that only changes if one of the dependencies has changed.

Basic Usage

import { useCallback, useState } from 'react';

function SearchComponent() {
  const [query, setQuery] = useState('');
  
  const handleSearch = useCallback((value) => {
    // This function is only recreated when query changes
    console.log('Searching for:', value);
    setQuery(value);
  }, [query]);
  
  return <SearchInput onSearch={handleSearch} />;
}

When to Use useCallback

Use useCallback when:

  • Passing callbacks to optimized child components
  • The callback is a dependency of other hooks
  • You want to prevent unnecessary re-renders

useMemo: Memoizing Values

The useMemo hook returns a memoized value that only recomputes when dependencies change.

Example

import { useMemo, useState } from 'react';

function ExpensiveComponent({ items }) {
  const [filter, setFilter] = useState('');
  
  const filteredItems = useMemo(() => {
    console.log('Filtering items...');
    return items.filter(item => 
      item.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);
  
  return (
    <div>
      <input 
        value={filter} 
        onChange={(e) => setFilter(e.target.value)} 
      />
      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

Common Pitfalls

Over-optimization

Don’t optimize prematurely:

// ❌ Bad: Unnecessary memoization
const value = useMemo(() => 2 + 2, []);

// ✅ Good: Simple calculations don't need memoization
const value = 2 + 2;

Missing Dependencies

// ❌ Bad: Missing dependency
const calculate = useCallback(() => {
  return value * 2;
}, []); // value is missing!

// ✅ Good: All dependencies included
const calculate = useCallback(() => {
  return value * 2;
}, [value]);

Best Practices

  1. Profile First: Use React DevTools Profiler to identify actual performance issues
  2. Measure Impact: Verify that memoization actually improves performance
  3. Keep Dependencies Minimal: Fewer dependencies mean better memoization
  4. Don’t Memoize Everything: These hooks add overhead

Real-World Example

function DataTable({ data, sortBy }) {
  const sortedData = useMemo(() => {
    console.log('Sorting data...');
    return [...data].sort((a, b) => {
      return a[sortBy] > b[sortBy] ? 1 : -1;
    });
  }, [data, sortBy]);
  
  const handleRowClick = useCallback((rowId) => {
    console.log('Row clicked:', rowId);
    // Handle row click
  }, []);
  
  return (
    <table>
      <tbody>
        {sortedData.map(row => (
          <TableRow 
            key={row.id} 
            data={row}
            onClick={handleRowClick}
          />
        ))}
      </tbody>
    </table>
  );
}

Conclusion

UseCallback and useMemo are powerful optimization tools, but use them wisely. Profile your application, identify bottlenecks, and apply memoization where it makes a measurable difference. Remember: premature optimization is the root of all evil!

E

Emily Watson

Published on March 10, 2024

Share: