React Memoization: When and How to Use useMemo, useCallback and React.memo

Oscar Bustos

A practical guide to understanding memoization in React and avoiding common pitfalls

6 min read

Memoization in React is often misused, leading to more complexity without actual performance benefits. Let’s understand when it’s truly needed and how to use it effectively.

What Problem Does Memoization Solve?

The core issue memoization addresses is referential equality in JavaScript:

const a = { id: 1 };
const b = { id: 1 };

console.log(a === b); // false
console.log({ id: 1 } === { id: 1 }); // false

This becomes important in React when comparing values between re-renders or in dependency arrays.

useMemo: Preserving Object References

// ❌ Bad: New object created every render
const Component = () => {
  const config = { theme: 'dark', size: 'large' };
  
  return <Child config={config} />;
}

// ✅ Good: Object reference preserved between renders
const Component = () => {
  const config = useMemo(() => ({ 
    theme: 'dark', 
    size: 'large' 
  }), []);
  
  return <Child config={config} />;
}

useCallback: Function Memoization

Common mistake: using useCallback unnecessarily.

// ❌ Unnecessary: Button doesn't use React.memo
const Component = () => {
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

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

// ✅ Necessary: When used in dependency arrays
const Component = () => {
  const [count, setCount] = useState(0);
  
  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  useEffect(() => {
    document.addEventListener('keypress', increment);
    return () => document.removeEventListener('keypress', increment);
  }, [increment]); // Used in dependencies

  return <button onClick={increment}>Count: {count}</button>;
}

React.memo: Component Memoization

The most powerful but also most dangerous memoization tool:

// ✅ Good use case: Pure, presentational component
const ExpensiveChart = React.memo(({ data, config }) => {
  // Complex chart rendering
  return <div>{/* Chart */}</div>;
});

// ❌ Bad use case: Component with children
const Layout = React.memo(({ children }) => {
  return <div>{children}</div>;
});

Common Pitfalls

1. Breaking Memoization with Inline Objects

// ❌ Bad: Memoization broken by inline styles
const Component = () => {
  return (
    <MemoizedChild 
      style={{ margin: 20 }} // New object every render
    />
  );
}

// ✅ Good: Move object outside or use useMemo
const Component = () => {
  const style = useMemo(() => ({ margin: 20 }), []);
  return <MemoizedChild style={style} />;
}

2. Missing Dependencies

// ❌ Bad: Missing dependency
const Component = ({ data }) => {
  const processData = useCallback(() => {
    return data.map(item => item.value);
  }, []); // data should be in deps array

  return <Child onProcess={processData} />;
}

// ✅ Good: Include all dependencies
const Component = ({ data }) => {
  const processData = useCallback(() => {
    return data.map(item => item.value);
  }, [data]);

  return <Child onProcess={processData} />;
}

Real World Example: Search Component

const SearchComponent = ({ onSearch }) => {
  // Memoize options object
  const options = useMemo(() => ({
    debounceTime: 300,
    minLength: 3,
    maxResults: 10
  }), []);

  // Memoize search handler for useEffect dependency
  const handleSearch = useCallback((term) => {
    if (term.length >= options.minLength) {
      onSearch(term);
    }
  }, [onSearch, options.minLength]);

  // Effect depends on memoized value
  useEffect(() => {
    const timer = setTimeout(handleSearch, options.debounceTime);
    return () => clearTimeout(timer);
  }, [handleSearch, options.debounceTime]);

  return <input onChange={e => handleSearch(e.target.value)} />;
}

// Only use React.memo if parent re-renders frequently
export default React.memo(SearchComponent);

When NOT to Use Memoization

  1. For simple primitive props
  2. Components that always need to re-render
  3. When the cost of comparison exceeds re-render cost
  4. Components with frequently changing dependencies

Key Takeaways

  1. Use useCallback when function is dependency in hooks
  2. Use useMemo for complex objects used in deps or memo’d components
  3. Use React.memo sparingly and only for pure components
  4. Profile performance before adding memoization

Is it a match?

If you need help or advice, feel free to drop us a message via social media or email

Contact me