VOOZH about

URL: https://blog.logrocket.com/react-memo/

⇱ React.memo explained: When to use it (and when not to) - LogRocket Blog


2025-02-26
2717
#react
Emmanuel John
201718
102
👁 Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

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?

👁 Image

In this guide, you’ll learn when to use React.memo — and when to avoid it.



🚀 Sign up for The Replay newsletter

The Replay is a weekly newsletter for dev and engineering leaders.

Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.

What is memoization?

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.

What is 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.

Skipping unnecessary re-rendering

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:

👁 React.memo Example

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.


Over 200k developers use LogRocket to create better digital experiences

👁 Image
Learn more →

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!" }));

👁 React.memo Example

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.

Applying 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 Example

Optimizing re-renders when props change

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.

Updating 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:

  • The ProductReview component will re-render only when the color prop changes
  • Changing the cart count will no longer trigger a re-render of the ProductReview component

This ensures better performance and prevents unnecessary renders, improving the responsiveness of the application:

👁 React.memo Example

Skipping expensive recalculations with useMemo

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.

Optimizing 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.

Adding dependencies to 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.


More great articles from LogRocket:


Memoizing function props

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.

Example without memoization

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.

Using 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.

Using useCallback

const handleCheckout = useCallback(
 (orderDetails) => {
 post('/checkout/' + orderId + '/buy', {
 orderDetails
 });
 }, [orderId]);

Memoizing array and object props

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.

Memoizing an object

const paymentOptions = useMemo(() => ({
 paymentMode: 'credit-card',
 amount: amount
}), [amount]);

Memoizing an array

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'];
}, []);

Optimizing component re-rendering when context changes

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>;
});

When you should avoid memoization

Avoid using memoization:

  1. If there is no noticeable lag in UI interactions.
  2. When using values inside useEffect
  3. To wrap JSX nodes

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]);

Using PureComponent in class components

For 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>;
 }
}

Potential issues with 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!' } });
};

Comparing 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

Conclusion

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!

Get set up with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID
  2. 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>
     
  3. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • NgRx middleware
    • Vuex plugin
Get started now
👁 Image
👁 Image
👁 Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

What is TSRX?: What JSX would look like if it were designed today

TSRX adds first-class control flow, conditional hooks, and scoped styles to React via a TypeScript compiler extension — no new framework required.

👁 Image
Ikeh Akinyemi
Jun 12, 2026 ⋅ 6 min read

How to add authentication to a React Native app with Better Auth

Learn how to build a full React Native auth system using Better Auth and Expo — with email/password login, Google OAuth, session persistence, and protected routes.

👁 Image
Chinwike Maduabuchi
Jun 9, 2026 ⋅ 13 min read

AI dev tool power rankings & comparison [June 2026]

Compare the top AI development tools and models of June 2026. View updated rankings, feature breakdowns, and find the best fit for you.

👁 Image
Chizaram Ken
Jun 8, 2026 ⋅ 11 min read

How to check username availability at scale with Bloom filters

Learn how Bloom filters reduce database lookups for username availability checks while preserving correctness at scale.

👁 Image
Rosario De Chiara
Jun 8, 2026 ⋅ 6 min read
View all posts

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 now