In React applications, performance optimization is crucial for delivering a smooth user experience. Every time a component re-renders, all the code inside it runs again — including complex calculations, data transformations, and expensive operations. This can lead to noticeable performance issues, especially in data-heavy applications.

The useMemo hook is React's built-in performance optimization tool that helps you optimize performance by memoizing (caching) the results of expensive calculations. By preventing unnecessary recalculations, useMemo can significantly improve your application's performance, particularly when dealing with:

  • Complex data transformations
  • Expensive sorting or filtering operations
  • Heavy computational tasks
  • Component prop optimization

What is useMemo Hook?

The useMemo hook is a performance optimization tool in React that allows you to memoize the result of a computation and recompute it only when its dependencies change.

This is helpful for avoiding expensive calculations on every render. Also, it can be used to optimize the rendering of child components by memoizing their props.


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

 const memoizedValue = useMemo(() => {
  // some expensive calculation
  return result;
}, [dependencies]);

In this example, the function inside useMemo will only run again if any of the values in the dependencies array change. If the dependencies remain the same, React will return the previously memoized value, avoiding unnecessary recalculations.

  • The function inside useMemo runs only when dependencies change.
  • The returned value (memoizedValue) is reused between renders.

useMemo Example

Here's an example of how to use the useMemo hook in a React component:

In the example where a calculation runs every time the component renders — even when it doesn't need to.

Without useMemo


import React, { useState } from "react";

const MyComponent = () => {
  const [count, setCount] = useState(0);

  const expensiveCalculation = () => {
    // Simulate an expensive calculation
    let result = 0;
    for (let i = 0; i < 1000000000; i++) {
      result += i;
    }
    return result;
  };

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <p>Result: {expensiveCalculation()}</p>
    </div>
  );
};

In this example, the expensiveCalculation function runs every time the component re-renders, which can slow down performance.

Now, let's optimize it using useMemo:

With useMemo


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

const MyComponent = () => {
  const [count, setCount] = useState(0);

  const expensiveCalculation = useMemo(() => {
    // Simulate an expensive calculation
    let result = 0;
    for (let i = 0; i < 1000000000; i++) {
      result += i;
    }
    return result;
  }, []); // Empty dependency array means it runs only once

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <p>Result: {expensiveCalculation}</p>
    </div>
  );
};

In this optimized version, the expensiveCalculation function runs only once when the component mounts, and its result is memoized. Subsequent renders will reuse the cached result, improving performance significantly.


useMemo for Filtering

useMemo is also great for optimizing derived data like filtered lists.

In this example, we'll filter a list of users based on a search query.


import { useState, useMemo } from "react";

function FilterExample() {
  const [query, setQuery] = useState("");
  const users = ["Avi", "John", "Sara", "Amit", "Kiran"];

  const filteredUsers = useMemo(() => {
    console.log("Filtering...");
    return users.filter((user) =>
      user.toLowerCase().includes(query.toLowerCase())
    );
  }, [query]);

  return (
    <div>
      <input
        type="text"
        placeholder="Search user..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <ul>
        {filteredUsers.map((user) => (
          <li key={user}>{user}</li>
        ))}
      </ul>
    </div>    
  );
}
      

In this example, the filteredUsers array is memoized using useMemo. The filtering logic runs only when the query state changes, avoiding unnecessary computations on every render.


When to use useMemo

Consider using useMemo in the following scenarios:

  • When you have expensive calculations that don't need to run on every render.
  • When you want to optimize the rendering of child components by memoizing their props.
  • When you want to avoid unnecessary re-renders of components that depend on complex data.

When not to use useMemo

Avoid using useMemo in these scenarios:

  • When the calculation is cheap and doesn't impact performance significantly.
  • When the dependencies change frequently, leading to frequent recalculations.
  • When it adds unnecessary complexity to your code without clear performance benefits.

Real-World Performance Example

Here's a practical example showing how useMemo can improve performance in a real-world scenario with data processing:


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

const ProductList = ({ products, category }) => {
  const [sortOrder, setSortOrder] = useState("asc");
  
  // Without useMemo - runs on every render
  // const sortedProducts = products
  //   .filter(product => product.category === category)
  //   .sort((a, b) => {
  //     return sortOrder === "asc" 
  //       ? a.price - b.price 
  //       : b.price - a.price;
  //   });

  // With useMemo - runs only when products, category, or sortOrder change
  const sortedProducts = useMemo(() => {
    console.log("Calculating sorted products..."); // Debug log
    return products
      .filter(product => product.category === category)
      .sort((a, b) => {
        return sortOrder === "asc" 
          ? a.price - b.price 
          : b.price - a.price;
      });
  }, [products, category, sortOrder]);

  return (
    <div>
      <button onClick={() => setSortOrder(
        order => order === "asc" ? "desc" : "asc"
      )}>
        Toggle Sort Order
      </button>
      <ul>
        {sortedProducts.map(product => (
          <li key={product.id}>
            {product.name} - ${product.price}
          </li>
        ))}
      </ul>
    </div>
  );
};
      

In this example, without useMemo, the filtering and sorting operations would run on every render, even when unrelated state changes occur. With useMemo, these expensive operations only run when the relevant dependencies change, providing significant performance benefits when dealing with large datasets.

Performance Impact

  • For a list of 1000 products: Without useMemo, each render could take 100-200ms
  • With useMemo: Subsequent renders take <1ms when dependencies haven't changed
  • Memory usage increase is negligible compared to the performance benefit