DEV Community

Cover image for Boost React Performance with useMemo: Stop Unnecessary Re-renders 🚀
Sivasubramaniyam
Sivasubramaniyam

Posted on

Boost React Performance with useMemo: Stop Unnecessary Re-renders 🚀

React Learning Guide

Optimize React with useMemo: Avoid Unnecessary Re-rendering 🚀

React's rendering process can become inefficient when components re-render unnecessarily. This is especially true for complex components with heavy computations or when rendering large lists. Luckily, React provides a handy hook called useMemo to help us optimize these scenarios by memoizing computed values. Let's dive into how useMemo can be leveraged to boost performance, with practical examples. 💡

What is useMemo? 🤔

useMemo is a React hook that memoizes the result of a computation. When you use useMemo, it stores the computed result and only recomputes it if one of its dependencies changes. This avoids unnecessary recalculations and, ultimately, unnecessary re-renders. 🎯

Syntax of useMemo 📝

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Enter fullscreen mode Exit fullscreen mode
  • First argument: A function that computes the value you want to memoize.
  • Second argument: An array of dependencies. If any of these dependencies change, the function will recompute the value.

When to Use useMemo 🕰️

Use useMemo when:

  1. Heavy Computation 🧮: Your component performs a computationally expensive task, like filtering a large dataset.
  2. Stable References 📏: When passing objects or functions as props to child components that rely on referential equality (===), useMemo can ensure that these references are stable unless needed to change.
  3. Large List Filter 📜: Optimizing a Large List Filter with useMemo.

Example 1: Memoizing a Heavy Computation ⚡

Let's consider a component that calculates the factorial of a number. Computing the factorial of large numbers can be resource-intensive, so it's a good candidate for useMemo.

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

const factorial = (n) => {
  console.log('Calculating factorial...');
  if (n < 0) return -1;
  if (n === 0) return 1;
  return n * factorial(n - 1);
};

const FactorialComponent = () => {
  const [number, setNumber] = useState(0);
  const [otherState, setOtherState] = useState(false);

  // Memoize the factorial computation
  const memoizedFactorial = useMemo(() => factorial(number), [number]);

  return (
    <div>
      <h2>Factorial Calculator</h2>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(parseInt(e.target.value, 10))}
      />
      <p>Factorial: {memoizedFactorial}</p>
      <button onClick={() => setOtherState(!otherState)}>
        Re-render Component
      </button>
    </div>
  );
};

export default FactorialComponent;
Enter fullscreen mode Exit fullscreen mode

In this example, factorial is memoized using useMemo. The factorial calculation only runs when number changes. Clicking the "Re-render Component" button does not trigger the factorial calculation because number hasn't changed. 🎉

Why it Matters 🧐

Without useMemo, the factorial calculation would run every time the component re-renders, regardless of whether number changed or not. This can degrade performance significantly in complex apps. 🚀

Example 2: Stable References for Child Components 🛠️

When you pass an object or function as a prop to a child component, React will re-render that child component whenever the parent re-renders. This happens because the reference to the object or function changes. Using useMemo can help stabilize these references. 📐

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

const ChildComponent = React.memo(({ data }) => {
  console.log('ChildComponent rendered');
  return <div>Data: {data.value}</div>;
});

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

  // Memoize the data object to prevent unnecessary re-renders of ChildComponent
  const memoizedData = useMemo(() => ({ value: count }), [count]);

  return (
    <div>
      <h2>Parent Component</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <ChildComponent data={memoizedData} />
    </div>
  );
};

export default ParentComponent;
Enter fullscreen mode Exit fullscreen mode

Explanation 📖

Here, ParentComponent renders ChildComponent and passes an object ({ value: count }) as a prop. If we didn't use useMemo, ChildComponent would re-render every time the parent re-renders, even if count hasn't changed, because the object reference would be different.

Using useMemo, the memoizedData object only changes when count changes, avoiding unnecessary re-renders of ChildComponent. 🎯

Example 3: Optimizing a Large List Filter with useMemo 🗂️

Let's say you have a component that displays a large list of products and allows users to search through them. Filtering the list can be computationally expensive, especially when the list is large. useMemo can be used here to optimize the filtering process so that it only recomputes the filtered list when the search query changes. 🛍️

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

// Sample large dataset of products
const products = Array.from({ length: 10000 }, (_, index) => ({
  id: index,
  name: `Product ${index + 1}`,
  description: `Description of product ${index + 1}`,
}));

const ProductList = () => {
  const [searchQuery, setSearchQuery] = useState('');

  // Memoize the filtered list to avoid unnecessary recomputations
  const filteredProducts = useMemo(() => {
    console.log('Filtering products...');
    if (!searchQuery) return products;
    return products.filter((product) =>
      product.name.toLowerCase().includes(searchQuery.toLowerCase())
    );
  }, [searchQuery]);

  return (
    <div>
      <h2>Product List</h2>
      <input
        type="text"
        placeholder="Search products..."
        value={searchQuery}
        onChange={(e) => setSearchQuery(e.target.value)}
      />
      <ul>
        {filteredProducts.map((product) => (
          <li key={product.id}>
            {product.name}: {product.description}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default ProductList;
Enter fullscreen mode Exit fullscreen mode

Explanation 💬

  1. Large Dataset: The products array simulates a large dataset with 10,000 items.
  2. Filtering Logic: Inside the component, the useMemo hook memoizes the filtered list of products based on the searchQuery.
  3. Dependency Array: The useMemo hook only recalculates the filtered list when searchQuery changes. If searchQuery remains the same, it returns the memoized list from the previous render.
  4. Console Logging: The console.log('Filtering products...') line shows when the filtering operation is triggered. You'll notice it only runs when searchQuery changes, not on every keystroke or re-render.

When NOT to Use useMemo 🚫

While useMemo can enhance performance, it also adds some complexity and may introduce bugs if used improperly. You should avoid using useMemo when:

  • The computation is cheap, and memoizing it won't bring noticeable performance improvements.
  • Premature optimization: It's often better to identify bottlenecks first rather than optimizing without profiling.

Conclusion 🏁

useMemo is a powerful tool for optimizing React applications by preventing unnecessary recalculations and re-renders. However, like any optimization technique, it should be used judiciously. It's most beneficial when dealing with expensive computations or when ensuring stable references for props.

By using useMemo effectively, you can make your React applications more performant and responsive, especially when dealing with complex rendering logic. 🏎️💨

Top comments (0)