React.memo & Avoiding Re-renders
React.memo is a higher-order component that tells React to skip re-rendering a component when its props haven't changed. It does a shallow comparison of props by reference. Paired with useMemo / useCallback , it prevents wasted renders in large trees. By the end you'll know exactly why a memoized component sometimes still re-renders — and how to fix it.
Learn React.memo & Avoiding Re-renders in our free React course — a beginner-friendly interactive lesson with runnable examples, a practice exercise and a…
Part of the free React course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
1️⃣ The Default: Children Re-render Too
By default, when a component re-renders, all of its children re-render — even if their props didn't change. Usually that's harmless and fast. But for an expensive child, or a child rendered many times, those wasted renders add up. React.memo wraps a component so React compares its props and skips the render when they're shallowly equal.
The demo models that shallow prop comparison so you can see what "props didn't change" really means:
Your turn — complete the per-key check that decides whether props are equal:
2️⃣ Why memo "Doesn't Work": Unstable References
The most common surprise: you wrap a child in memo , but it still re-renders every time. The cause is almost always a prop that's a new reference on every render — an inline object, array, or arrow function. Shallow comparison sees a different reference and gives up.
The demo proves it: an inline object is never equal across renders, but a stable reference is:
3️⃣ Custom Comparison Functions
memo accepts an optional second argument: a function (prevProps, nextProps) => boolean . Return true to skip the render (props are "equal"), false to re-render. Use it when shallow comparison isn't enough — but keep it cheap, since it runs every render.
Beware: the return value is inverted from what you might expect. true means "they match, don't re-render." A common bug is returning the opposite and re-rendering constantly.
Reorder Challenge
These lines define a memoized child and the stable props its parent must pass. Put them in a correct order:
Quick Recall
1) A memoized child gets style={{ color: "red" }} inline. Does memo skip its render?
No. The inline object is a new reference every render, so the shallow comparison fails and the child re-renders.
2) In a custom comparison function, what does returning true mean?
"Props are equal — skip the re-render." It is the inverse of what people often expect.
3) Does React.memo compare props shallowly or deeply by default?
Shallowly — each prop is compared with Object.is . Nested objects with equal contents but different references count as different.
Common Errors (and the fix)
- memo'd child still re-renders: a prop is a new reference each render. Wrap objects/arrays in useMemo and functions in useCallback .
- Inverted comparison function: returning true means "skip render." Returning the wrong boolean re-renders constantly (or never).
- Memoizing cheap components: the comparison itself costs. Don't wrap tiny leaf nodes that render rarely.
- Passing children as a prop: JSX children are usually new each render, so a memo'd wrapper around {children} rarely skips.
📋 Quick Reference
Goal
Syntax
Memoize a component
const C = memo(function C(props) { ... });
Stable object prop
const o = useMemo(() => ({...}), []);
Stable function prop
const f = useCallback(() => fn(), []);
Custom compare
memo(C, (prev, next) => prev.id === next.id)
Frequently Asked Questions
Mini-Challenge: Will It Skip?
Use the shallow comparison to decide whether memo would skip a re-render. Compare two prop objects where one shares a reference and one is inline. Predict each result first.
🎉 Lesson Complete
- ✅ By default, children re-render with their parent
- ✅ React.memo skips a render when props are shallowly equal
- ✅ Inline objects/arrays/functions break memoization — stabilize them
- ✅ memo pairs with useMemo and useCallback
- ✅ A custom compare returns true to skip
- ✅ Next lesson: Lifting State Up
Practice quiz
What does React.memo do?
- Caches a component's state
- Memoizes a value like useMemo
- Skips re-rendering when props are shallowly equal to the previous props
- Prevents all re-renders forever
Answer: Skips re-rendering when props are shallowly equal to the previous props. memo wraps a component so React reuses its last output when props haven't changed (shallowly).
How does React.memo compare props by default?
- Shallowly, with Object.is per prop
- Deeply, recursively
- By JSON.stringify
- By component name
Answer: Shallowly, with Object.is per prop. It compares each prop with Object.is; nested objects with equal content but new references count as different.
By default, when a component re-renders, its children…
- Never re-render
- Re-render only if memoized
- Unmount and remount
- Re-render too, even if their props didn't change
Answer: Re-render too, even if their props didn't change. Children re-render with their parent by default; memo lets React skip that when props are unchanged.
Why does a memoized child still re-render every time here: <Avatar style={{ borderRadius: 8 }} />?
- memo only works on classes
- The inline object is a new reference each render, so shallow comparison fails
- style isn't a valid prop
- It needs a key
Answer: The inline object is a new reference each render, so shallow comparison fails. Inline objects/arrays/functions create a new reference every render, breaking the shallow comparison.
How do you stabilize an object prop so memo can skip?
useMemo returns a stable reference across renders so the shallow comparison passes.
How do you stabilize a function prop passed to a memoized child?
- useMemo
- useState
- useCallback
- bind it inline
Answer: useCallback. useCallback keeps the same function identity across renders.
In a custom comparison function passed to memo, returning true means…
- Re-render the component
- Props are equal — skip the re-render
- Throw an error
- Force a deep compare
Answer: Props are equal — skip the re-render. memo's compare returns true to skip (props 'equal') and false to re-render — the inverse of what many expect.
What is the signature of memo's optional second argument?
- (state, action) => state
- (a, b) => number
- () => void
- (prevProps, nextProps) => boolean
Answer: (prevProps, nextProps) => boolean. It's a comparison function (prevProps, nextProps) => boolean controlling whether to skip the render.
Should you wrap every component in React.memo?
- Yes, always
- No — the comparison has a cost; use it for components that render often or are expensive, and profile first
- Yes, for performance
- Only class components
Answer: No — the comparison has a cost; use it for components that render often or are expensive, and profile first. memo adds a comparison each render; apply it selectively and measure before optimizing.
Why does a memo'd wrapper around {children} rarely skip rendering?
- children can't be a prop
- memo ignores children
- JSX children are usually a new reference each render
- children are always equal
Answer: JSX children are usually a new reference each render. Children passed as JSX are typically new each render, so the shallow comparison fails.
Continue this course
- Previous: useMemo & useCallback
- Next: Lifting State Up