Re-Renders Are React's Superpower, Not a Disease to Cure

I was going to move the series straight into how an app looks and feels. But something has been bugging me for a while, and it's architectural enough that skipping it would be dishonest. So this is a short detour, and it's about a panic I keep seeing: someone opens the React Profiler, watches a component re-render when they didn't expect it to, and immediately starts demolishing their code to make the re-render stop. Props get removed. Components get split in strange places. memo gets sprayed over everything. All to drive a number down.
Here's the thing I want to say plainly, because almost nobody says it: re-rendering is not React's weakness. It's React's superpower. And a lot of the "optimizations" people perform to avoid re-renders are quietly worse for the codebase than the re-renders ever were.
Rendering is the model
Step back and remember what React actually is. Your UI is a function of state. State changes, React re-runs the function, produces a new description of the UI, and reconciles it against the last one. That re-run — the render — is not an accident or an overhead bolted onto React. It is React. The entire programming model, the reason React felt like a relief after manual DOM manipulation, is that you get to describe "what the UI looks like for this state" and never wire up the updates yourself.
Compare it to a signals-based library — Solid, or the fine-grained-reactivity family. There the model is different by design:
// Solid-style: fine-grained. The component function runs ONCE. function Counter() { const [count, setCount] = createSignal(0); // this text node subscribes to count directly; only IT updates return <button onClick={() => setCount(count() + 1)}>{count()}</button>; }
// React: the component function re-runs on every state change. function Counter() { const [count, setCount] = useState(0); // the whole function runs again; React diffs the result return <button onClick={() => setCount(count + 1)}>{count}</button>; }
Both are legitimate architectures. Signals update surgically and never re-run the component; React re-runs the component and diffs. But notice: the React version's "re-run everything and figure out what changed" is the very thing that makes React's mental model so simple. You don't track dependencies by hand. You don't wire subscriptions. You describe UI-as-a-function-of-state and let the render do the work. Treating that render as a defect is like buying a bird and being annoyed that it keeps flying. The flying is the point.
A re-render is not React doing something wrong. It's React doing the one thing it exists to do. The goal was never zero renders — it was the freedom to stop thinking about updates.
So what's actually bad, then?
I'm not saying render count never matters. It does — but the problem is narrower than the panic suggests. A bad situation is a render storm at scale: every mousemove re-rendering every component on the page, a giant list re-rendering all thousand rows because one row's checkbox changed, an expensive tree re-running on every keystroke in an unrelated input. That's real, and it's worth fixing.
But a component re-rendering because its parent re-rendered, doing a bit of cheap work and producing the same DOM? That's usually nothing. React is fast at this. The reconciler is built for it. Most of the re-renders people agonize over cost less than the useMemo they add to prevent them. The panic treats every render as a cost when the real question is only ever "is this render actually expensive, and does it actually happen often enough to matter?"
Which is why the word matters so much. People say they want to minimize re-renders. The word you actually want is optimize. Minimize is a religion — zero renders at any cost. Optimize means find the balance: spend effort on the renders that hurt, and leave the cheap ones alone. Minimizing is something you do to a number. Optimizing is something you do to a system, with judgment about where the cost really is.
The trade nobody prices: architecture for render count
Here's where this becomes an architecture article and not a performance tip, because the real damage isn't the wasted effort — it's what people break to save a render. Let me give you a few shapes I keep seeing.
Contorting to remove a "guilty" prop. A component re-renders because a prop changes. So instead of leaving the honest data flow in place, someone restructures so the component no longer receives that prop — it reaches into a store or context to grab the value itself, so the parent can't "trigger" it. The render is gone. But now the component's real dependency is hidden. It looks independent and isn't. You've taken a visible prop — a traceable edge in your dependency graph — and turned it into an invisible reach-out, the exact anti-pattern the composition and DI article argued against. You optimized a render and de-optimized your ability to understand the code.
Memo everywhere as a reflex. Every component wrapped in memo, every value in useMemo, every function in useCallback — not because anything was measured, but because renders are scary now.
// this "optimization" costs more than it saves const Row = memo(function Row({ item, onSelect }: RowProps) { const handle = useCallback(() => onSelect(item.id), [onSelect, item.id]); const style = useMemo(() => ({ paddingLeft: item.depth * 8 }), [item.depth]); return <div style={style} onClick={handle}>{item.label}</div>; });
For a trivial row, the memoization machinery — the comparison, the dependency arrays, the mental overhead — is more expensive than just rendering the div. And every one of those dependency arrays is now a thing that can go subtly wrong and a thing the next person has to maintain. You bought a micro-optimization with a permanent tax on changeability.
Splitting on render boundaries instead of responsibility boundaries. This is the worst one, because it looks like good decomposition. To isolate a re-render, someone fractures a cohesive component into oddly-shaped pieces — not because those pieces are meaningful units, but because the split happens to stop a render from propagating. Now a single logical concern is smeared across three components that only make sense together, which is precisely the smeared-component problem wearing a performance badge. You decomposed for the profiler, not for the reader.
In each case the pattern is identical: a local win on render count, paid for with a structural loss that the profiler will never show you.
The part everyone forgets: the component has to be maintained
The fixation on renders has tunnel vision. It optimizes for one moment — the render — and forgets that this component has a whole life ahead of it. It's going to grow. It's going to be reused somewhere the "clever" structure doesn't fit. Someone's going to need to change it in six months. The "ideal" low-render component is very often a maintenance liability, because its shape was dictated by a performance metric instead of by what the thing actually is.
This is where the balance genuinely lives, and it's context-dependent. Is this a marketing landing page? Then honestly, do nothing — render "waste" on a page that mounts once and barely updates is a non-problem, and every hour spent memoizing it is wasted. But a large, high-load, long-lived application — the kind with a real model / view-model / view separation, data fetching, aggregation, and preparation layers — that's where the trade-off between architecture and render performance is a real engineering decision. And the answer there is deliberate: you optimize the hot paths you've measured, and you refuse to sacrifice the structure that keeps the app maintainable for renders that don't hurt.
The screw you can't reach
I was reassembling my daughter's computer recently and it made this click for me. Ten screws on the back, off comes the panel, and everything inside is modular — the drive, the memory, the fan all lift out independently. Fifteen minutes to clean it. I remembered the old laptops I used to take apart years ago, where you had to remove things in one exact sequence, and if you wanted to reach one component you first had to dismantle four unrelated ones sitting on top of it. Miss the order and you're stuck.
Bad render-driven architecture is the old laptop. To save a render you bury a "screw" — you tuck a dependency somewhere awkward, you weld two concerns together, you hide a prop — and later, when you need to change that part, you can't get to it without taking apart everything piled on top. Good architecture is my daughter's computer: parts have obvious places, and when you want to move, replace, or fix one, you just can. The whole reason we care about structure is that day later, when requirements change and you need to reach in cleanly.
That serviceability is worth far more than a render count, and it's exactly what people trade away when the profiler scares them.
Where I land
So my actual rule is unglamorous. Treat re-rendering as the normal, healthy heartbeat of a React app, not a symptom. Measure before you optimize — find the renders that are genuinely expensive and genuinely frequent, and fix those, with the smallest intervention that works. And never, ever trade the reachability of your architecture for a number in a profiler, because one of those you can feel every day you maintain the code, and the other you mostly imagined.
Renders are React's superpower. Point it where it helps, tune it where it hurts, and stop apologizing for the thing that makes the whole model work.
With that off my chest, the series gets back on track — into how an app looks and feels at an architectural level, starting with the one that looks like a CSS detail and is really a systems problem: theming and dark mode, done so it doesn't flash on load, doesn't drift across components, and doesn't fight server rendering. That's next.
If you've got a component in your app that was reshaped purely to cut a re-render, go look at it now and ask an honest question: was the render actually expensive, or did you just make the code harder to change to satisfy a profiler? Tell me what you find — I suspect the ratio surprises people.
More than a blog post
I share frontend news and the reasoning behind it throughout the day. Pick the language that feels natural to you.