React components re-render whenever their parent updates, even if their props remain unchanged. This can cause performance issues, especially with large datasets or complex UI updates. React.memo helps optimize performance by memoizing components and preventing unnecessary re-renders. But does it always work?
In this guide, you’ll learn when to use React.memo
— and when to avoid it.
React.memo
, and how does it work?React.memo
is a higher-order component (HOC) that memoizes functional components, preventing them from re-rendering unless their props change.
React.memo
improve performance?Yes, it helps optimize performance by skipping unnecessary re-renders. However, it should be used only when performance issues arise, as unnecessary memoization can add complexity.
React.memo
?Use React.memo
when:
React.memo
component still re-rendering?Even with React.memo
, a component will still re-render if:
useEffect
or subscriptions that trigger updatesReact.memo
vs.
useMemo
vs. useCallback
— what’s the difference?Memoization is a performance optimization technique that caches the result of a function and returns the cached value for subsequent calls with the same inputs.
In React, memoization helps prevent unnecessary re-renders of components handling large datasets, resource-heavy operations, or expensive calculations.
React.memo
?React.memo
is a React API that caches functional components on the first render and returns the cached component as long as the props remain unchanged. If the props change, the component re-renders.
Under the hood, React.memo
uses Object.is
for a shallow comparison of the previous and new props. If they are identical, the cached version is returned; otherwise, the component re-renders.
Let’s consider an ecommerce case study where a product detail page displays reviews (a review component) for each product. The review component may re-render when unrelated parts of the product page update. This happens because, by default, React re-renders a child component whenever the parent component state changes.
Create a ProductDetailPage.jsx
component in your React project and add the following:
//ProductDetailPage.jsx import React, { useState } from "react"; const ProductDetailPage = () => { const [cartCount, setCartCount] = useState(0); const handleAddToCart = () => { setCartCount(cartCount + 1); alert("Product added to cart!"); }; return ( <div className="max-w-4xl mx-auto p-6"> {/* Product Section */} <div className="grid grid-cols-1 md:grid-cols-2 gap-8"> {/* Product Image */} <div> <img src="https://res.cloudinary.com/muhammederdem/image/upload/q_60/v1536405217/starwars/item-2.webp" alt="Product" className="w-full h-auto rounded-lg shadow-md" /> </div> {/* Product Details */} <div> <h1 className="text-2xl font-bold mb-4">Awesome Product</h1> <p className="text-gray-700 mb-4"> This is a detailed description of the awesome product. It has amazing features and great quality. </p> <p className="text-xl font-semibold mb-4">$49.99</p> <button onClick={handleAddToCart} className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition" > Add to Cart </button> <p className="mt-2 text-sm text-gray-500">Cart Count: {cartCount}</p> </div> </div> {/* Review Section */} <ProductReview /> </div> ); }; const ProductReview = () => { const reviews = [ { id: 1, author: "John Doe", rating: 5, comment: "Amazing product!" }, { id: 2, author: "Jane Smith", rating: 4, comment: "Very good quality." }, { id: 3, author: "Alex Johnson", rating: 3, comment: "It's decent for the price.", }, ]; console.log("ProductReview was rendered at", new Date().toLocaleTimeString()); return ( <div className="mt-10"> <h2 className="text-xl font-bold mb-4">Customer Reviews</h2> <div className="space-y-4"> {reviews.map((review) => ( <div className="p-4 border rounded-lg bg-gray-50 shadow-sm"> <p className="font-semibold">{review.author}</p> <p className="text-yellow-500">{"⭐".repeat(review.rating)}</p> <p className="text-gray-600">{review.comment}</p> </div> ))} </div> </div> ); }; export default ProductDetailPage;
The ProductDetailPage
component is the parent component of the ProductReview
component. It maintains a cartCount
state to track how many times the user clicks the “Add to Cart” button, updating the count and displaying a confirmation alert. The ProductReview
component renders a list of customer reviews and logs the render time to the console for tracking performance.
Running the project should result in the following:
Notice how the ProductReview
component re-renders each time the user adds the product to the cart. This re-render is unnecessary because updating the cart does not impact the customer reviews.
Since the number of reviews in the ProductReview
component is small, the performance impact is negligible. However, let’s examine the effect when the component handles thousands of reviews. Update the reviews
array as follows:
const reviews = Array.from({ length: 10000 }, () => ({ id: Math.random(), author: "John Doe", rating: 5, comment: "Amazing product!" }));
At this point, adding the product to the cart introduces a noticeable lag. If you click the Add to Cart button multiple times in quick succession, the entire page may freeze. This happens because React re-renders all 10,000 reviews each time the state updates, significantly slowing down the application.
Clearly, excessive re-renders negatively impact performance. We can optimize this behavior by using React.memo
to prevent unnecessary re-renders of the ProductReview
component.
React.memo
To optimize performance, wrap the ProductReview
component with React.memo
as follows:
import React, { memo} from "react"; const ProductReview = memo(() => { const reviews = Array.from({ length: 10000 }, () => ({ id: Math.random(), author: "John Doe", rating: 5, comment: "Amazing product!" })); console.log("ProductReview was rendered at", new Date().toLocaleTimeString()); return ( <div className="mt-10"> <h2 className="text-xl font-bold mb-4">Customer Reviews</h2> <div className="space-y-4"> {reviews.map((review) => ( <div key={review.id} className="p-4 border rounded-lg bg-gray-50 shadow-sm"> <p className="font-semibold">{review.author}</p> <p className="text-yellow-500">{"⭐".repeat(review.rating)}</p> <p className="text-gray-600">{review.comment}</p> </div> ))} </div> </div> ); }); export default ProductDetailPage;
With this modification, ProductReview
will only re-render when its props change, effectively preventing unnecessary re-renders when updating the cart. This simple adjustment significantly improves the application’s responsiveness and ensures better performance:
React.memo
can be used to optimize re-renders by ensuring a component updates only when its props change. Let’s demonstrate this by adding a feature that allows users to change the text color of both the product name and the review header.
ProductDetailPage
Modify the ProductDetailPage
component to include a dropdown that allows users to select a text color:
const ProductDetailPage = () => { const [cartCount, setCartCount] = useState(0); const [color, setColor] = useState(""); const handleChange = (event) => { setColor(event.target.value); }; const handleAddToCart = () => { setCartCount(cartCount + 1); alert("Product added to cart!"); }; return ( <div className="max-w-4xl mx-auto p-6"> {/* Product Section */} <div className="grid grid-cols-1 md:grid-cols-2 gap-8"> {/* Product Image */} {/* Product Details */} <div> <h1 className={`text-2xl font-bold mb-4 ${color}`}> Awesome Product </h1> <p className="text-gray-700 mb-4"> This is a detailed description of the awesome product. It has amazing features and great quality. </p> <p className="text-xl font-semibold mb-4">$49.99</p> <button onClick={handleAddToCart} className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition" > Add to Cart </button> <p className="my-3 text-sm text-gray-500">Cart Count: {cartCount}</p> <div> <p className="font-medium">Change Text Color</p> <select value={color} onChange={handleChange} className="border rounded-lg p-2 bg-white shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500" > <option value="" disabled> -- Choose an option -- </option> <option value="text-blue-700">Blue</option> <option value="text-red-700">Red</option> <option value="text-green-700">Green</option> </select> </div> </div> </div> {/* Review Section */} <ProductReview color={color} /> </div> ); };
Here, we’ve added a dropdown select to choose different text colors and pass the color
prop to the ProductReview
component.
To access the color
prop, make the following changes to the ProductReview
component:
const ProductReview = memo(({color}) => { const reviews = Array.from({ length: 10000 }, () => ({ id: Math.random(), author: "John Doe", rating: 5, comment: "Amazing product!" })); console.log("ProductReview was rendered at", new Date().toLocaleTimeString()); return ( <div className="mt-10"> <h2 className={`text-xl font-bold mb-4 ${color}`}>Customer Reviews</h2> <div className="space-y-4"> {reviews.map((review) => ( <div key={review.id} className="p-4 border rounded-lg bg-gray-50 shadow-sm"> <p className="font-semibold">{review.author}</p> <p className="text-yellow-500">{"⭐".repeat(review.rating)}</p> <p className="text-gray-600">{review.comment}</p> </div> ))} </div> </div> ); });
With these modifications:
ProductReview
component will re-render only when the color
prop changesProductReview
componentThis ensures better performance and prevents unnecessary renders, improving the responsiveness of the application:
Did you notice the lag in the UI after choosing a text color? This happens because when the color
prop changes, thousands of reviews are regenerated using Array.from()
and then re-rendered. To fix this issue, we’ll use the useMemo
hook to cache the result of the reviews
computation.
ProductReview
with useMemo
const ProductReview = memo(({ color }) => { const reviews = useMemo(() => Array.from({ length: 10000 }, () => ({ id: Math.random(), author: "John Doe", rating: 5, comment: "Amazing product!" })), []); console.log("ProductReview was rendered at", new Date().toLocaleTimeString()); return ( <div className="mt-10"> <h2 className={`text-xl font-bold mb-4 ${color}`}>Customer Reviews</h2> <div className="space-y-4"> {reviews.map((review) => ( <div key={review.id} className="p-4 border rounded-lg bg-gray-50 shadow-sm"> <p className="font-semibold">{review.author}</p> <p className="text-yellow-500">{"⭐".repeat(review.rating)}</p> <p className="text-gray-600">{review.comment}</p> </div> ))} </div> </div> ); });
With this modification, the reviews
array is computed only on the first render, significantly improving performance.
useMemo
const [size, setSize] = useState(10); const reviews = useMemo(() => Array.from({ length: size }, () => ({ id: Math.random(), author: "John Doe", rating: 5, comment: "Amazing product!" })), [size]);
Now, useMemo
will recompute reviews
each time the size
dependency changes.
By default, function definitions in React components change on every re-render. If a function is passed as a prop to a memoized component, memoization will not work unless the function itself is memoized.
export default function Cart({ orderId }) { function handleCheckout(orderDetails) { post('/checkout/' + orderId + '/buy', { orderDetails }); } return <Checkout onSubmit={handleCheckout} />; }
In this case, handleCheckout
is re-created every render, causing unnecessary re-renders in Checkout
.
useMemo
export default function Cart({ orderId }) { const handleCheckout = useMemo(() => (orderDetails) => { post('/checkout/' + orderId + '/buy', { orderDetails }); }, [orderId]); return <Checkout onSubmit={handleCheckout} />; }
A cleaner approach is to use useCallback
.
useCallback
const handleCheckout = useCallback( (orderDetails) => { post('/checkout/' + orderId + '/buy', { orderDetails }); }, [orderId]);
Similar to functions, array and object definitions change on every re-render. If an object or array is passed as props to a memoized component, memoization will not work unless they are also memoized.
const paymentOptions = useMemo(() => ({ paymentMode: 'credit-card', amount: amount }), [amount]);
const cardTypes = useMemo(() => ['credit-card', 'debit-card'], []);
By memoizing objects and arrays, we ensure that components relying on them do not re-render unnecessarily, leading to better performance and efficiency in React applications.
Just like the function definition, every array and object definition in a React component changes on every rerender and if an object or array is passed as props to a memoized component, memoization will not work.
Here is how to memoize an object:
const paymentOptions = useMemo(() => { return { paymentMode: 'credit-card', amount: amount }; }, [amount]);
Here is how to memoize an array:
const cardTypes = useMemo(() => { return ['credit-card', 'debit-card']; }, []);
By default, React re-renders components when there’s a change in context. This also applies to memoized components, as demonstrated in the following code snippet:
import React, { createContext, useContext, useState, memo } from 'react'; const UserContext = createContext(); export const App = () => { const [user, setUser] = useState({ name: 'John', age: 30 }); return ( <UserContext.Provider value={user}> <UserName /> <p style={{ color: 'white' }}>Age: {user.age}</p> <button onClick={() => setUser({ ...user, age: user.age + 1 })}> Increment Age </button> </UserContext.Provider> ); }; const UserName = memo(() => { const { name } = useContext(UserContext); console.log('UserName rendered'); return <div style={{ color: 'white' }}>User Name: {name}</div>; });
Each time the user’s age
value changes, the memoized UserName
component still re-renders, even though the name
value remains unchanged. To prevent this, pass only the required part of the context as a prop to the memoized component.
import React, { createContext, useContext, useState, memo } from 'react'; const UserContext = createContext(); export const App = () => { const [user, setUser] = useState({ name: 'John', age: 30 }); return ( <UserContext.Provider value={user}> <UserDisplay /> <p style={{ color: 'white' }}>Age: {user.age}</p> <button onClick={() => setUser({ ...user, age: user.age + 1 })}> Increment Age </button> </UserContext.Provider> ); }; const UserDisplay = () => { const { name } = useContext(UserContext); return <UserName name={name} />; }; const UserName = memo(({ name }) => { console.log('UserName rendered'); return <div style={{ color: 'white' }}>User Name: {name}</div>; });
Avoid using memoization:
useEffect
Memoizing values used inside useEffect
is unnecessary. Instead, move the value inside the effect:
useEffect(() => { const options = { serverUrl: 'https://localhost:1234', roomId: roomId }; const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [roomId]);
Wrapping JSX nodes in useMemo
prevents conditional rendering:
const children = useMemo(() => <ProductList items={products} />, [products]);
PureComponent
in class componentsFor class-based components, React.memo
, useMemo
, and useCallback
will not work. Instead, use PureComponent
, which re-renders only when its props change:
class PureComponentExample extends React.PureComponent { render() { console.log('Pure Component rendered'); return <div>{this.props.value}</div>; } }
PureComponent
If you pass objects or arrays that mutate without changing their reference, PureComponent
may not detect changes:
import React, { PureComponent } from 'react'; class ChildComponent extends PureComponent { render() { console.log('ChildComponent rendered'); return <div>Message: {this.props.data.message}</div>; } } export default class App extends React.Component { state = { data: { message: 'Hello' } }; updateMessage = () => { const { data } = this.state; data.message = 'Hello, World!'; // Mutating the state object this.setState({ data }); // Setting the same object reference }; render() { return ( <div> <ChildComponent data={this.state.data} /> <button onClick={this.updateMessage}>Update Message</button> </div> ); } }
Because this.setState({ data })
does not create a new object reference, PureComponent
cannot detect the update, and ChildComponent
will not re-render. To fix this, always create a new reference:
updateMessage = () => { this.setState({ data: { ...this.state.data, message: 'Hello, World!' } }); };
React.memo
to useMemo
, useCallback
, and PureComponent
The following table will help you know when to use React.memo
, useMemo
, useCallback
.
Feature | React.memo |
useMemo |
useCallback |
PureComponent |
---|---|---|---|---|
Usage | Prevent component re-renders | Avoid recalculating values | Avoid recreating functions | Avoid re-rendering in class components |
Scope | Component-level | Value-level | Function-level | Class components only |
In this article, we explored how React.memo
can improve app performance by skipping unnecessary re-renders. We covered real-world use cases, best practices, and when to avoid memoization. Additionally, we compared React.memo
with useMemo
, useCallback
, and PureComponent
to understand their specific use cases.
By applying these techniques wisely, you can significantly enhance the efficiency of your React applications!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how React’s useCallback hook boosts performance by memoizing functions and preventing unnecessary re-renders with practical examples and best practices.
Learn the characteristics of the virtual document object model (DOM), explore its benefits in React, and review a practical example.
You might start with if...else
statements. But as your logic grows more complex, you’ll discover the switch statement in JavaScript is simply better for handling multiple conditions in a clean and readable way.
Use React Hook Form and Zod to build a multi-step form component in React that handles input validation, tracks form progress, and more.