DEV Community

Cover image for The Cost of Re-Renders: How to Make React Apps Actually Reactive
Sachin Maurya
Sachin Maurya

Posted on

The Cost of Re-Renders: How to Make React Apps Actually Reactive

Introduction

React’s biggest selling point has always been simple:

“It’s fast because it re-renders efficiently.”

But here’s the uncomfortable truth — React isn’t fast by default.
It’s just forgiving.

If you don’t design your state carefully, React’s virtual DOM can easily become your bottleneck.
And the more complex your app grows, the more expensive every unnecessary re-render becomes.

Let’s dissect why re-renders happen, how to avoid them, and what being “reactive” actually means in 2025.


1. Every Re-Render Has a Cost

Every React render triggers a reconciliation cycle:

  • Components re-run,
  • Hooks re-evaluate,
  • JSX re-diffing happens,
  • Child components may re-render too.

It’s all virtual, but it’s not free.
When your app scales to hundreds of components, these micro re-renders add up — especially when tied to global or context-based state.


2. The Silent Villain: Shared State

Developers often push everything into Context or Redux for “simplicity.”
The problem? Every value change triggers re-renders across all consumers, even if most don’t use that specific part of the state.

// Common pattern
const ThemeContext = createContext();

function App() {
  return (
    <ThemeContext.Provider value={{ darkMode, toggleDarkMode }}>
      <Header />
      <Sidebar />
      <Content />
    </ThemeContext.Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Even if only Header uses darkMode, all children inside the provider re-render when it changes.
That’s death by a thousand re-renders.


3. Memoization Isn’t a Silver Bullet

You might say, “I’ll just memo everything.”
Sure — but memoization has its own cost.

  • React.memo() prevents re-renders only if props are shallow-equal.
  • useMemo() and useCallback() cache computations, but add complexity and memory overhead.

If you’re wrapping half your tree in React.memo(), you’re fighting React — not optimizing it.

The better solution is state locality — keeping state as close as possible to where it’s used.


4. Localize, Slice, and Select

The more granular your state, the fewer components React needs to re-render.

That’s why libraries like Zustand, Jotai, and Valtio are so effective — they allow components to subscribe only to the data they need.

Example (Zustand):

const useStore = create(set => ({
  count: 0,
  increment: () => set(state => ({ count: state.count + 1 })),
}));

function Counter() {
  const count = useStore(state => state.count);
  return <p>{count}</p>;
}
Enter fullscreen mode Exit fullscreen mode

Here, React re-renders only components using count, not the entire app.
That’s true reactivity, not reconciliation.


5. Fine-Grained Reactivity Is the Future

React’s model is coarse-grained: it re-renders entire components when state changes.
But new paradigms (like Signals in Solid, Qwik, and even React Canary builds) use fine-grained reactivity — updating only the exact DOM nodes that depend on changed data.

This shift reduces the cost of re-renders dramatically, making UI feel instantaneous.

If React 18 was about concurrency, React 19+ will be about reactivity.


6. Measuring Re-Render Costs

You can’t optimize what you can’t see.
Use the React DevTools “Profiler” to measure where re-renders happen — you’ll often be surprised.

Look out for:

  • Components re-rendering without prop/state changes
  • Context-heavy trees
  • Missing key attributes
  • Inline object/array props

Identifying hot spots is half the battle.


7. The Real Goal: Flow, Not Frames

When your React app feels slow, it’s rarely the algorithm — it’s usually the render flow.
True performance comes from structuring your app around reactive data flow, not just faster diffing.

It’s not about “preventing re-renders”; it’s about making every render count.


Conclusion

React doesn’t slow down your app — your architecture does.

Stop chasing useMemo everywhere.
Start designing state locality, reactive data flows, and granular subscriptions.

That’s how modern UIs achieve “speed” — not by skipping renders, but by rendering intelligently.


Why I Wrote This

I wrote this after noticing how often React developers (my past self included) equate “performance” with “memoization.”
But performance doesn’t come from avoiding renders — it comes from understanding when they happen and why.

If this post helped you see React’s rendering model differently, I’d love to hear your take —
How do you handle re-renders in your projects?
Do you rely more on memoization or on state locality patterns (like Zustand/Jotai)?

Drop your thoughts below — let’s make this a conversation rather than just another React article.

Top comments (0)