Boost React Performance with useMemo: Stop Unnecessary Re-renders ๐
Sivasubramaniyam
Posted on September 19, 2024
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]);
- 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:
- Heavy Computation ๐งฎ: Your component performs a computationally expensive task, like filtering a large dataset.
-
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. -
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;
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;
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;
Explanation ๐ฌ
-
Large Dataset: The
products
array simulates a large dataset with 10,000 items. -
Filtering Logic: Inside the component, the
useMemo
hook memoizes the filtered list of products based on thesearchQuery
. -
Dependency Array: The
useMemo
hook only recalculates the filtered list whensearchQuery
changes. IfsearchQuery
remains the same, it returns the memoized list from the previous render. -
Console Logging: The
console.log('Filtering products...')
line shows when the filtering operation is triggered. You'll notice it only runs whensearchQuery
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. ๐๏ธ๐จ
Posted on September 19, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 19, 2024