Mastering React’s useMemo Hook: Boost Performance with Real-World Code Examples

4 min read

Mastering React’s useMemo Hook: Boost Performance with Real-World Code Examples

A concise, code-focused tutorial on optimizing React apps using the useMemo hook to avoid unnecessary computations.

1. Introduction

React’s useMemo hook memoizes expensive calculations, recomputing only when dependencies change. It’s essential for performance in apps with heavy computations, like data processing or complex derivations.

Introduced in React 16.8, useMemo prevents re-runs on every render, saving resources in large components.

We’ll cover the problem it solves, the solution, detailed implementations with a real-life e-commerce filtering scenario, and key takeaways. Heavy on code for quick learning.

2. The Problem

In React, components re-render on state/prop changes, re-executing all code—including expensive functions. This causes performance issues:

  • Lagging UIs in data-heavy apps.
  • Unnecessary CPU usage for unchanged results.
  • Battery drain on mobile.

Example: A component computing Fibonacci on every render:

function SlowComponent({ n }) {
  const fib = computeFib(n); // Expensive, runs every render
  return <div>Fib({n}): {fib}</div>;
}

function computeFib(n) {
  if (n <= 1) return n;
  return computeFib(n - 1) + computeFib(n - 2);
}

Renders slow for large n, even if n unchanged.

3. The Solution

useMemo memoizes values:

import { useMemo } from 'react';

const memoizedValue = useMemo(() => expensiveFunction(a, b), [a, b]);
  • Runs callback only if dependencies [a, b] change.
  • Returns cached value otherwise.

Best for pure functions with costly ops. Combine with useCallback for memoized callbacks.

Pros: Improves render speed, reduces side effects. Cons: Overuse adds overhead; use profiling to identify needs.

4. Implementation

Let’s use a real-life scenario: An e-commerce product list with filtering and sorting. Without memoization, filtering a large array (e.g., 1000+ products) re-runs on every render, causing jank during typing or state updates.

App structure:
• ProductList: Displays filtered/sorted products.
• Filters: Search input, category select.
• Expensive ops: Filter by search, sort by price.

Step 1: Basic Setup Without useMemo (Slow)

// Products.js - Sample data
export const products = Array.from({ length: 1000 }, (_, i) => ({
  id: i,
  name: `Product ${i}`,
  price: Math.random() * 100,
  category: ['Electronics', 'Books', 'Clothing'][Math.floor(Math.random() * 3)],
}));
// ProductList.js
import { useState } from 'react';
import { products } from './Products';

function ProductList() {
  const [search, setSearch] = useState('');
  const [category, setCategory] = useState('All');

  // Expensive: Filters and sorts every render
  const filteredProducts = products
    .filter(p => p.name.toLowerCase().includes(search.toLowerCase()))
    .filter(p => category === 'All' || p.category === category)
    .sort((a, b) => a.price - b.price);

  return (
    <div>
      <input
        type="text"
        value={search}
        onChange={e => setSearch(e.target.value)}
        placeholder="Search products..."
      />
      <select value={category} onChange={e => setCategory(e.target.value)}>
        <option>All</option>
        <option>Electronics</option>
        <option>Books</option>
        <option>Clothing</option>
      </select>
      <ul>
        {filteredProducts.map(p => (
          <li key={p.id}>{p.name} - ${p.price.toFixed(2)}</li>
        ))}
      </ul>
    </div>
  );
}

export default ProductList;

Issue: Typing in search re-renders, re-filtering all 1000 items unnecessarily if products unchanged.

Step 2: Optimize with useMemo

// Optimized ProductList.js
import { useState, useMemo } from 'react';
import { products } from './Products';

function ProductList() {
  const [search, setSearch] = useState('');
  const [category, setCategory] = useState('All');

  const filteredProducts = useMemo(() => {
    console.log('Computing filtered products...'); // Logs only on dep change
    return products
      .filter(p => p.name.toLowerCase().includes(search.toLowerCase()))
      .filter(p => category === 'All' || p.category === category)
      .sort((a, b) => a.price - b.price);
  }, [search, category]); // Dependencies

  return (
    <div>
      <input
        type="text"
        value={search}
        onChange={e => setSearch(e.target.value)}
        placeholder="Search products..."
      />
      <select value={category} onChange={e => setCategory(e.target.value)}>
        <option>All</option>
        <option>Electronics</option>
        <option>Books</option>
        <option>Clothing</option>
      </select>
      <ul>
        {filteredProducts.map(p => (
          <li key={p.id}>{p.name} - ${p.price.toFixed(2)}</li>
        ))}
      </ul>
    </div>
  );
}

export default ProductList;

Now, computation runs only when search or category changes. Other re-renders (e.g., parent updates) use cached value.

Example 2: Memoizing Complex Objects

const stats = useMemo(() => ({
  count: filteredProducts.length,
  avgPrice: filteredProducts.reduce((sum, p) => sum + p.price, 0) / filteredProducts.length || 0,
}), [filteredProducts]);

Add to return: <p>Items: {stats.count}, Avg Price: ${stats.avgPrice.toFixed(2)}</p>

Example 3: With useCallback for Memoized Functions

import { memo } from 'react';

// Child component
const ProductItem = memo(({ product, onClick }) => {
  return <li onClick={() => onClick(product.id)}>{product.name} - ${product.price.toFixed(2)}</li>;
});

// In ProductList
const handleClick = useCallback((id) => {
  console.log(`Clicked ${id}`);
}, []); // Empty deps if no dependencies

// In map: <ProductItem key={p.id} product={p} onClick={handleClick} />

useMemo for values, useCallback for functions—prevents child re-renders.

Real-Life Tips

  • Use React DevTools Profiler to spot slow renders.
  • Avoid large dependency arrays; optimize upstream.
  • For API data: Memoize after fetching.
  • Test with large datasets (e.g., 10k items) to see gains.

This e-commerce example shows useMemo cutting render times by 80%+ in benchmarks.

5. Conclusion

useMemo is key for performant React apps, caching computations to skip unnecessary work.

From basic filtering to complex derivations, it shines in data-intensive UIs like dashboards or shops.

Prioritize it for expensive pure functions. Combine with memo, useCallback for full optimization.

🤞 Never miss a story from us, get weekly updates to your inbox!

Leave a Reply

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