Published on 1.8.2019
As your React applications grow in complexity, performance becomes an important consideration. React makes it easy to build dynamic UIs, but in larger applications, unnecessary re-renders can slow down performance. In this post, we’ll explore two React optimizations — React.memo
and useMemo
— and discuss how and when to use each to prevent unnecessary re-renders and optimize your app’s performance.
Understanding Re-renders in React
Before we dive into optimizations, let’s understand what causes re-renders. React re-renders a component whenever its state or props change. While re-renders are necessary to keep the UI up to date, they can also become expensive if not managed properly, especially with complex or large components.
When your component re-renders more than necessary, it can lead to performance bottlenecks. For instance, a component that receives unchanged props or doesn’t need to update might still re-render, wasting unnecessary resources.
Fortunately, React provides tools like React.memo
and useMemo
to help us avoid these performance pitfalls.
React.memo
– Preventing Unnecessary Re-renders of Functional Components
React.memo
is a higher-order component that memoizes a component, preventing it from re-rendering unless its props change. If the props remain the same between renders, React will skip the re-render and reuse the previous output.
It’s particularly useful when you have a functional component that receives props but doesn’t rely on internal state or context that changes frequently.
Example: Optimizing a List Component with React.memo
Let’s say we have a simple list of items, and each list item is a functional component. Without optimization, if the parent component re-renders, all of the list items would re-render, even if their props haven’t changed.
import React, { useState } from 'react';
const ListItem = ({ item }) => {
console.log('Rendering ListItem:', item);
return <div>{item}</div>;
};
const MemoizedListItem = React.memo(ListItem);
const List = () => {
const [count, setCount] = useState(0);
const items = ['Apple', 'Banana', 'Cherry'];
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<div>Count: {count}</div>
<div>
{items.map((item, index) => (
<MemoizedListItem key={index} item={item} />
))}
</div>
</div>
);
};
export default List;
Explanation:
ListItem
Component: This component simply renders an item from the list. Without optimization, this component would re-render every time the parentList
component re-renders.React.memo
: We wrap theListItem
component inReact.memo
to prevent unnecessary re-renders. Now, even if theList
component re-renders (e.g., due to a state change),ListItem
will only re-render if itsitem
prop changes.- Memoization Effect: We’ve added a button that increments the
count
state. Although thecount
state changes, theListItem
components won’t re-render because their props (the list items) haven’t changed.
Key Takeaway: Use React.memo
to optimize functional components that only depend on props and avoid re-rendering when props remain unchanged.
useMemo
– Memoizing Expensive Calculations
useMemo
is a hook that memoizes the result of an expensive calculation, ensuring it’s only recalculated when its dependencies change. It’s typically used to avoid re-computing derived data or expensive calculations unnecessarily on each render.
Example: Optimizing Expensive Calculations with useMemo
Imagine you have a list of numbers and need to filter out even numbers. If the list of numbers is large, it can be an expensive operation. Let’s use useMemo
to optimize this calculation.
import React, { useState, useMemo } from 'react';
const ExpensiveFilter = () => {
const [filterText, setFilterText] = useState('');
const [items, setItems] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
// Memoizing the filtered list to avoid recalculating it on each render
const filteredItems = useMemo(() => {
console.log('Filtering items...');
return items.filter((item) => item.toString().includes(filterText));
}, [filterText, items]);
return (
<div>
<input
type="text"
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
placeholder="Filter items"
/>
<ul>
{filteredItems.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
};
export default ExpensiveFilter;
Explanation:
useMemo
: We useuseMemo
to memoize the result of thefilter
operation. The filter will only be re-run if eitherfilterText
oritems
changes. This prevents the filtering logic from running on every re-render of the component.- Performance Gain: Without
useMemo
, the filtering logic would be re-executed on every keystroke, which could be expensive for large lists. WithuseMemo
, it’s only recalculated when necessary.
Key Takeaway: Use useMemo
when you have an expensive calculation or transformation that you want to avoid repeating unnecessarily on every render.
When to Use React.memo
vs. useMemo
React.memo
: UseReact.memo
to prevent unnecessary re-renders of a component when its props haven’t changed. It’s ideal for optimizing child components that receive props from a parent component.useMemo
: UseuseMemo
to optimize expensive calculations that depend on specific variables. It helps prevent recalculating values or running expensive functions on every render.
When Not to Use useMemo
and React.memo
While React.memo
and useMemo
are powerful tools, they’re not always necessary. Using them prematurely can lead to unnecessary complexity. As a general rule:
- Don’t use them for trivial optimizations. React’s built-in rendering optimizations (like re-rendering only when necessary) are already pretty efficient for most cases.
- Measure first: If you notice performance issues in a specific area of your app, measure the impact of optimizations like
React.memo
anduseMemo
to ensure they are worth the trade-off.
Conclusion
Optimizing React applications is all about avoiding unnecessary work. Both React.memo
and useMemo
provide useful ways to prevent unnecessary re-renders and recalculations, improving performance.
- Use
React.memo
to optimize functional components that only depend on props. - Use
useMemo
to memoize expensive calculations that depend on specific values or arrays.
By applying these optimizations thoughtfully, you can ensure your React applications stay fast and responsive, even as they grow in complexity.
Happy coding!