Avoiding wasted renders
🎯You'll be able to
Find wasted renders with the Profiler and fix only the ones that matter, using moved state, memo and stable props.
🏠The everyday picture
A school doesn't reprint every student's report card because one student's address changed. It reprints only the cards that actually changed.
📘What it really is
- Most re-renders are cheap. Measure first with the Profiler; optimise only components that are slow and re-render often.
- Ways to reduce wasted work:
- move state down: keep state in the smallest component that needs it, so fewer components re-render;
- pass children: a component that wraps
childrendoesn't re-render them when its own state changes; React.memo: skips re-rendering a component when its props are unchanged;useMemo: remembers an expensive calculation until its inputs change;-
useCallback: keeps the same function between renders, so a memoised child sees "unchanged" props. -
memoonly helps if props really stay the same. A new object or function created on every render (style={{...}},onClick={() => ...}) makes the props look changed each time. -
Very long lists (thousands of rows) are better handled by virtualisation: rendering only the rows on screen.
- Newer React versions include a compiler that can add some of this memoisation automatically; check whether your project uses it before adding it by hand.
📝A worked example
On Kirana Express, typing in search makes the product list stutter on a ₹8,000 Android phone. The Profiler shows ProductList
taking 45 ms per keystroke, because it filters and sorts 2,000 products and renders every card.
| Change | Time per keystroke |
|---|---|
| Starting point | 45 ms |
useMemo on the filtered and sorted list |
30 ms |
memo on ProductCard, with useCallback for onAdd |
12 ms |
| Virtualised list (only ~20 cards rendered) | 4 ms |
Each step was measured, and the team stopped once typing felt instant on the test phone.
🌍Where it's used
- Search-as-you-type on large catalogues.
- Dashboards with many charts.
- Long feeds and tables in admin tools.
📝Case scenario
A developer wraps every component in memo and every function in useCallback "to be safe". The code becomes harder to read, and
one memoised component shows stale data because a dependency was missed. You remove the blanket memoisation, profile, and keep it
only on the two components that measurably needed it.
🖱️Try it
Load 2,000 sample products, profile typing in the search box, then apply one technique at a time from the table above. Record the time after each change on a throttled CPU (DevTools → Performance → CPU 4× slowdown).
⚠️Common mistakes
- Adding memo everywhere first. Sign: more code and no measurable gain. Fix: profile, then optimise the slow component that re-renders often.
- Passing new objects or functions every render to a memoised child. Sign: memo never skips. Fix: move the value out, or keep it stable with useMemo or useCallback.
🛠️For your project
Project 3's slow Deals page recalculates on every keystroke. Profile it before you change anything, and record the before figure.
✅Quick check
What should you do before adding memo or useMemo?
Show the answer
Measure with the Profiler to confirm a component is actually slow and re-rendering often.
Why can memo fail to help when a parent passes onClick={() => add(p)}?
Show the answer
A new function is created on every render, so the props look changed each time.
