Not memoizing expensive computations using useMemo

Loading

Optimizing Performance with useMemo in React

A common performance oversight in React applications is failing to memoize expensive computations, which can lead to unnecessary re-renders and sluggish UI. Here’s how to properly use useMemo to optimize your components.

The Core Problem

Problematic implementation without memoization:

function ProductList({ products, filters }) {
  // ❌ Expensive computation runs on every render
  const filteredProducts = products.filter(p => 
    p.price >= filters.minPrice && 
    p.price <= filters.maxPrice
  );

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

Issues this causes:

  • Unnecessary recomputation on every render
  • Performance degradation with large datasets
  • Janky UI during interactions
  • Reduced battery life on mobile devices

Correct Implementation with useMemo

1. Basic Memoization

function ProductList({ products, filters }) {
  // ✅ Only recomputes when products or filters change
  const filteredProducts = useMemo(() => {
    return products.filter(p => 
      p.price >= filters.minPrice && 
      p.price <= filters.maxPrice
    );
  }, [products, filters]);

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

2. Complex Computations

function Dashboard({ transactions }) {
  // ✅ Memoizes multiple expensive calculations
  const { total, average, chartData } = useMemo(() => {
    const total = transactions.reduce((sum, t) => sum + t.amount, 0);
    const average = total / transactions.length;
    const chartData = processTransactionsForChart(transactions);

    return { total, average, chartData };
  }, [transactions]);

  return (
    <div>
      <Summary total={total} average={average} />
      <Chart data={chartData} />
    </div>
  );
}

When to Use useMemo

Appropriate Use Cases:

  1. Filtering/sorting large arrays
  2. Complex calculations
  3. Formatting/transforming data
  4. Deriving state from props
  5. Component props that don’t need to change often

When Not to Use:

  1. Simple calculations (addition, string concatenation)
  2. Primitive values that don’t involve computation
  3. Cases where recomputation is cheaper than memoization overhead

Common Mistakes and Solutions

❌ Missing Dependencies

const result = useMemo(() => {
  return compute(a, b); // Uses a and b
}, [a]); // ❌ Missing b dependency

Fix: Include all dependencies

const result = useMemo(() => {
  return compute(a, b);
}, [a, b]); // ✅ Correct dependencies

❌ Overusing useMemo

const fullName = useMemo(() => {
  return `${firstName} ${lastName}`; // ❌ Too simple to memoize
}, [firstName, lastName]);

Fix: Only memoize expensive operations

const fullName = `${firstName} ${lastName}`; // ✅ Simple concatenation

�Using useMemo for Components

const MemoizedComponent = useMemo(() => {
  return <ExpensiveComponent data={data} />; // ❌ Wrong pattern
}, [data]);

Fix: Use React.memo instead

const MemoizedComponent = React.memo(ExpensiveComponent);

Advanced Patterns

1. Combining with useCallback

function Parent({ items }) {
  // Memoize both data and handlers
  const processedItems = useMemo(() => processItems(items), [items]);
  const handleSelect = useCallback((id) => {
    // Handle selection
  }, []);

  return <Child items={processedItems} onSelect={handleSelect} />;
}

2. Custom Comparison Function

function useDeepMemo(factory, deps) {
  const prevDeps = useRef(deps);

  if (!deepEqual(prevDeps.current, deps)) {
    prevDeps.current = deps;
  }

  return useMemo(factory, [prevDeps.current]);
}

// Usage
const result = useDeepMemo(() => compute(a, b), [a, b]);

3. Memoizing Context Value

function AppProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);

  // Memoize context value to prevent unnecessary re-renders
  const contextValue = useMemo(() => ({
    state,
    dispatch
  }), [state]);

  return (
    <AppContext.Provider value={contextValue}>
      {children}
    </AppContext.Provider>
  );
}

Performance Measurement

Verify optimizations with:

  1. React DevTools Profiler
  2. console.time()/console.timeEnd()
  3. Performance tab in browser devtools

Example measurement:

function Component() {
  console.time('compute');
  const data = useMemo(() => expensiveCompute(), [deps]);
  console.timeEnd('compute'); // Logs computation time

  return /* ... */;
}

Key Takeaways

  1. Memoize expensive computations with useMemo
  2. Include all dependencies in the dependency array
  3. Don’t overuse – apply only where beneficial
  4. Combine with React.memo for component optimization
  5. Measure before/after to validate improvements

Proper use of useMemo can significantly improve performance in React applications by avoiding unnecessary recomputations during renders. However, it should be applied judiciously where measurable benefits exist.

Leave a Reply

Your email address will not be published. Required fields are marked *