Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

React.memo In Depth

React.memo In Depth

~15 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

"React for Beginners" introduced useMemo/useCallback – caching values and functions. React.memo is their counterpart at the component level: an entire component only re-renders when its props have ACTUALLY changed.

The core problem: a parent render cascades to child renders

React's default behavior, WITHOUT React.memo: when a parent component re-renders, ALL its child components re-render too – REGARDLESS of whether their props changed. That's usually harmless (re-rendering is cheap), but can become noticeable with expensive computations inside a child component, or with very many child components (see the next chapter: list virtualization).

Applying React.memo: wrapping ProductCard

ProductCard is a perfect example: ProductListPage re-renders on EVERY search keystroke (query state change) – and so far, that meant EVERY single ProductCard re-rendered too, even when its own props (name, price, ...) stayed unchanged:

src/components/ProductCard.jsx
import { memo, useState } from 'react';

function ProductCard({ name, price, imageUrl, onSelect, onAddToCart }) {
  const [isFavorite, setIsFavorite] = useState(false);

  function handleFavoriteClick(event) {
    event.stopPropagation();
    setIsFavorite(!isFavorite);
  }

  function handleAddToCartClick(event) {
    event.stopPropagation();
    onAddToCart();
  }

  return (
    <div className="product-card" onClick={onSelect}>
      <img src={imageUrl} alt={name} className="product-card__image" />
      <div className="product-card__info">
        <h3 className="product-card__name">{name}</h3>
        <p className="product-card__price">${price.toFixed(2)}</p>
      </div>
      <button className="product-card__favorite" onClick={handleFavoriteClick}>
        {isFavorite ? '♥' : '♡'}
      </button>
      <button className="product-card__add-to-cart" onClick={handleAddToCartClick}>
        Add to Cart
      </button>
    </div>
  );
}

export default memo(ProductCard);

Only one change matters: export default memo(ProductCard) instead of export default ProductCard. From now on, before every re-render, React compares ALL of ProductCard's props via a shallow comparison (Object.is on EACH individual prop value) against the previous call – if they are IDENTICAL, the render is skipped entirely.

The most common trap: inline functions as props

Now for the catch that makes React.memo disappointing for many beginners: look at ProductListPage.jsx – it passes onAddToCart={{() => dispatch(addItem(...))}} as an inline arrow function. On EVERY render of ProductListPage, a NEW function is created – even though its content looks identical, it's a DIFFERENT reference as far as Object.is is concerned. So React.memo sees "onAddToCart changed" and re-renders anyway – memo alone would have accomplished practically NOTHING here.

The fix: stabilizing onAddToCart with useCallback

To make React.memo effective, the function passed down itself needs to stay STABLE across render passes – exactly what useCallback from "React for Beginners" chapter 10 provides. Here's the relevant excerpt from ProductListPage.jsx, extended with useCallback (the rest of the file stays as shown in chapter 29):

import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
// ... remaining imports unchanged

// ... inside the component, AFTER useDispatch():
const handleAddToCart = useCallback(
  (product) => dispatch(addItem({ sku: product.sku, name: product.name, price: product.price })),
  [dispatch]
);

// ... in the JSX:
<ProductCard
  key={product.sku}
  name={product.name}
  price={product.price}
  imageUrl={product.imageUrl}
  onSelect={() => navigate(`/products/${product.sku}`)}
  onAddToCart={() => handleAddToCart(product)}
/>

Achtung: Watch out: onAddToCart={{() => handleAddToCart(product)}} is STILL a new inline function on every render, because it captures product from the closure! useCallback only stabilizes handleAddToCart ITSELF (the function that takes a product parameter), not the per-product-specific inline version of it. To FULLY fix this, ProductCard itself would need sku/product as a prop and would call onAddToCart(product) internally using its own sku – a restructuring beyond the scope of this chapter. The key point here: understanding React.memo also means understanding how EASILY it can be rendered accidentally useless.

When React.memo is actually worth it

  • The component is EXPENSIVE to render (complex computation, large DOM tree, many children) AND
  • it FREQUENTLY receives the same props again (e.g. because its parent often re-renders for other reasons) AND
  • its props can actually be kept REFERENCE-STABLE (primitives like numbers/strings are automatically; objects/arrays/functions need useMemo/useCallback).

Achtung: React.memo is NOT free – the props comparison itself costs time. For small, cheap-to-render components with often-changing props, React.memo can actually make the app SLOWER (comparison cost > saved render cost). Use the profiler from the last chapter to measure BEFORE and AFTER adding memo, instead of blindly applying it everywhere.

Bonus: a custom comparison function

memo() optionally accepts a second argument – a custom comparison function, for when the default shallow comparison doesn't fit (e.g. when a prop is an array whose CONTENT, not its reference, should be compared):

export default memo(ProductCard, (prevProps, nextProps) => {
  // returning true = "equal, do NOT re-render"
  return prevProps.name === nextProps.name && prevProps.price === nextProps.price;
});

Tipp: Rule of thumb: memo() without a second argument covers 95% of cases. A custom comparison function is a tool for rare special cases, not the default path – it's itself a potential source of bugs (forget one prop in the comparison, and a genuine update can be incorrectly skipped).