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:
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).