DEV Community

Atlas Whoff
Atlas Whoff

Posted on

React Performance: Avoiding Unnecessary Re-Renders With memo and useMemo

React Performance: Avoiding Unnecessary Re-Renders With memo and useMemo

Re-renders aren't inherently bad — React is fast. But unnecessary re-renders of expensive components cause jank. Here's how to fix them.

When to Optimize

Profile first. Use React DevTools Profiler to identify components that:

  • Render frequently (dozens of times per second)
  • Are expensive to render (large lists, complex calculations)
  • Re-render when their props haven't changed

Don't add memo everywhere — it adds overhead and rarely helps for cheap components.

React.memo: Skip Re-Renders When Props Unchanged

// Without memo: re-renders every time parent renders
function ExpensiveChart({ data }: { data: number[] }) {
  return <svg>{/* complex rendering */}</svg>;
}

// With memo: only re-renders when data changes
const ExpensiveChart = React.memo(function ExpensiveChart({ data }: { data: number[] }) {
  return <svg>{/* complex rendering */}</svg>;
});

// Custom comparison (when shallow equality isn't enough)
const ExpensiveChart = React.memo(
  function ExpensiveChart({ data }: { data: number[] }) {
    return <svg>{/* */}</svg>;
  },
  (prevProps, nextProps) => {
    return prevProps.data.length === nextProps.data.length &&
           prevProps.data.every((v, i) => v === nextProps.data[i]);
  }
);
Enter fullscreen mode Exit fullscreen mode

useMemo: Cache Expensive Calculations

function Dashboard({ transactions }: { transactions: Transaction[] }) {
  // Recalculates on EVERY render without useMemo
  const stats = transactions.reduce((acc, t) => {
    // expensive aggregation...
  }, initialStats);

  // With useMemo: only recalculates when transactions changes
  const stats = useMemo(
    () => transactions.reduce((acc, t) => { /* expensive */ }, initialStats),
    [transactions]
  );

  return <StatsDisplay stats={stats} />;
}
Enter fullscreen mode Exit fullscreen mode

useCallback: Stable Function References

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

  // Bad: new function reference on every render
  // breaks React.memo on Child
  const handleClick = () => setCount(c => c + 1);

  // Good: stable reference
  const handleClick = useCallback(() => setCount(c => c + 1), []);

  return <MemoizedChild onClick={handleClick} />;
}
Enter fullscreen mode Exit fullscreen mode

The Virtualization Alternative

For long lists (500+ items), virtualization beats memo:

import { FixedSizeList } from 'react-window';

function VirtualizedList({ items }: { items: Item[] }) {
  return (
    <FixedSizeList height={600} itemCount={items.length} itemSize={50} width='100%'>
      {({ index, style }) => (
        <div style={style}>{items[index].name}</div>
      )}
    </FixedSizeList>
  );
  // Only renders ~12 visible items, regardless of list size
}
Enter fullscreen mode Exit fullscreen mode

Performance optimization patterns — memo, virtualization, code splitting — are part of the production React setup in the AI SaaS Starter Kit.

Top comments (0)