DEV Community

Abhay Singh Kathayat
Abhay Singh Kathayat

Posted on

Optimizing React Performance with useMemo for Caching Expensive Calculations

useMemo Hook for Caching in React

The useMemo hook is used in React to optimize performance by memoizing the result of expensive calculations. This means that useMemo will cache the result of a computation and only recompute it when its dependencies (typically state or props) change. It can be particularly useful when dealing with expensive calculations, rendering large lists, or handling complex logic that doesn't need to be recalculated on every render.

How useMemo Works

useMemo accepts two arguments:

  1. A function that returns a computed value.
  2. A dependency array that determines when the memoized result should be recalculated.

The function inside useMemo is only called when one of the values in the dependency array changes. If none of the dependencies change, React returns the cached value instead of re-running the calculation.

Syntax

const memoizedValue = useMemo(() => {
  // expensive calculation
  return result;
}, [dependencies]);
Enter fullscreen mode Exit fullscreen mode

When to Use useMemo

  • Expensive Calculations: When you need to perform an expensive calculation and want to avoid recalculating it on every render.
  • Preventing Unnecessary Re-renders: For large lists or deeply nested components, useMemo can help avoid recalculating the rendered output unless necessary.
  • Optimizing Derived State: When your component's state depends on a calculation that does not need to be recalculated on every render.

Example 1: Using useMemo to Cache Expensive Calculations

Imagine you have a component that calculates the sum of a large array of numbers. Without useMemo, the calculation would happen every time the component re-renders, which could lead to performance issues.

Without useMemo:

import React, { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const numbers = Array.from({ length: 10000 }, (_, index) => index + 1);

  const calculateSum = () => {
    console.log("Calculating sum...");
    return numbers.reduce((acc, num) => acc + num, 0);
  };

  return (
    <div>
      <p>Sum: {calculateSum()}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

In this example, the calculateSum function runs on every render, even though the numbers array hasn't changed. This is inefficient for large data sets.

With useMemo:

import React, { useState, useMemo } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const numbers = Array.from({ length: 10000 }, (_, index) => index + 1);

  const calculateSum = useMemo(() => {
    console.log("Calculating sum...");
    return numbers.reduce((acc, num) => acc + num, 0);
  }, [numbers]); // Only recalculates if 'numbers' array changes

  return (
    <div>
      <p>Sum: {calculateSum}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

In this updated example, the calculateSum function is only recomputed if the numbers array changes. The console.log inside useMemo will only show up the first time the component renders and if the numbers array changes.

Example 2: Avoiding Recalculation of Filtered List

Consider a scenario where you need to filter a large list based on a search term. Filtering the list can be expensive, so you can use useMemo to prevent unnecessary recalculations.

Without useMemo:

import React, { useState } from 'react';

function App() {
  const [searchTerm, setSearchTerm] = useState('');
  const items = Array.from({ length: 10000 }, (_, index) => `Item ${index}`);

  const filteredItems = items.filter(item =>
    item.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <div>
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="Search items"
      />
      <ul>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

In this example, the filtering operation runs every time the App component re-renders, even if the searchTerm hasn't changed.

With useMemo:

import React, { useState, useMemo } from 'react';

function App() {
  const [searchTerm, setSearchTerm] = useState('');
  const items = Array.from({ length: 10000 }, (_, index) => `Item ${index}`);

  const filteredItems = useMemo(() => {
    return items.filter(item =>
      item.toLowerCase().includes(searchTerm.toLowerCase())
    );
  }, [searchTerm, items]); // Only recomputes when 'searchTerm' or 'items' change

  return (
    <div>
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="Search items"
      />
      <ul>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Here, the filtered list is memoized, meaning it will only be recomputed when the searchTerm or items change. If only the searchTerm changes, the filtering operation won't be run on every render, improving performance.

Key Points to Remember

  1. Avoid Overuse: Using useMemo can improve performance but should be used sparingly. Adding useMemo unnecessarily can lead to more complexity and overhead than simply allowing React to re-render.

  2. Dependencies Array: The second argument to useMemo is important for controlling when the memoized value should be recalculated. Always ensure that you include the proper dependencies in the array to prevent issues.

  3. Shallow Comparison: By default, useMemo only performs shallow comparison of dependencies. For more complex comparisons, you may need to manually handle it.

  4. Performance Gain: useMemo is helpful for optimizing expensive calculations or operations that involve large data sets, filtering, or sorting operations.

When to Use useMemo

  • Expensive calculations that don't change unless specific inputs change.
  • Optimizing derived state that depends on complex calculations.
  • Memoizing components or props that are passed into child components to avoid unnecessary re-renders.

Conclusion

useMemo is a valuable hook for caching expensive computations and improving performance in React applications. By memoizing values, React can avoid unnecessary recalculations and minimize the rendering cost, especially in large applications or those with complex data.

Top comments (0)