React useEffect is one of the first hooks people learn after useState.
It’s also one of the most misunderstood.
For many teams, useEffect becomes a kind of universal solvent: whenever something feels off in a component, an effect is added until it behaves. The code “works”, the UI updates, and the PR gets merged.
What’s less visible is the cost: extra renders, delayed state convergence, and components that only make sense if you simulate React’s lifecycle in your head.
This post isn’t about banning useEffect. It’s about understanding when its use creates work React never asked you to do.
Core mistake (using effects to derive state)
Consider a simple example.
This looks reasonable. It is also inefficient by construction.
What React actually does?
-
Initial render
totalis0- React paints
0to the screen
-
Effect phase
setTotal(amount + tax)runs
-
Second render
totalis now correct- React paints again
You have forced two renders for a value that is a pure calculation.
If a value can be calculated during render, putting it in useEffect guarantees at least one
extra render.
Render time calculation is not an optimization
The fix is boring, which is a compliment.
- Now React does exactly one render.
- No effect phase.
- No state reconciliation.
- No moment where the UI is “almost right”.
This is not a micro-optimization. It’s aligning with how React is designed to work.
Props → State Syncing (accidental double rendering)
Another common pattern looks like this:
Every time user.name changes, the component renders twice:
- Render with stale state
- Effect runs
- Render again with updated state
Worse, you’ve now split a single fact (user.name) into two sources of truth.
In small components this is easy to miss. In larger ones, it becomes a breeding ground for bugs that only appear “sometimes”.
The correct solution is usually the simplest one:
Why this scales poorly?
One unnecessary effect might cost you one extra render, that doesn’t sound dramatic...but effects compose.
If you have:
- multiple derived values
- multiple syncing effects
- components rendered in lists
The extra work multiplies, React still renders quickly, but the app starts to feel oddly sluggish:
- more commits
- more layout work
- more chances to momentarily show stale UI
This is how performance problems sneak in—not with a bang, but with “harmless” patterns repeated everywhere.
When useEffect is actually the right tool?
useEffect exists for a reason. It’s just a narrower reason than many people assume.
It’s appropriate when React needs to coordinate with something outside itself:
- event listeners
- subscriptions
- timers
- imperative libraries
This code cannot run during render. It depends on the external world. That’s exactly what effects are for.
What they are not for is finishing a render that could have been correct in the first place.
Simple heuristic
When you reach for useEffect, ask:
Could this logic run during render without breaking anything?
- If yes, it probably should.
- If no, because it touches the outside world—then an effect makes sense.
If your answer depends on timing, ordering, or “waiting for React”, that’s a signal to rethink the component, not to add another dependency to the array.
Conclusion
useEffect is not a default mechanism for making components work. It’s an escape hatch for side effects that React cannot model declaratively.
Using it to derive state or sync props introduces:
- extra renders,
- temporal complexity,
- and components that are harder to reason about than they need to be.
React already gives you a deterministic render phase. If you lean into it, many effects simply disappear.
And when they don’t, at least you’ll know why they’re there.