Stop Blaming useEffect: React’s Real State Problem
how to manage state
I’ve been building with React since before Hooks were cool. Over the years, I’ve written my fair share of messy components — the kind where useEffect calls pile up like duct tape patches over logic that doesn’t quite fit. So when Ryan Carniato, creator of SolidJS, posted this earlier today on X, it hit a nerve:
“Everyone blames useEffect, but the real problem is useState. We model data as discrete variables instead of cohesive systems, and then we wonder why our effects get tangled.”
He’s right. The problem isn’t side effects — it’s the way we model state.
The Overuse of useState: A Quiet Chaos
React makes it too easy to break your data into tiny islands. You start with this:
const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [email, setEmail] = useState(''); const [isValid, setIsValid] = useState(false);
Each variable feels neat and local — until you realize isValid depends on all the others. Now every change triggers a cascade of updates, and suddenly you’re syncing them with a useEffect just to keep things consistent.
It works, sure. But it’s brittle. You can’t glance at the code and see the data flow. Dependencies hide inside effects, and refactors become landmines. Ryan’s point is simple: this isn’t good modeling — it’s fragmentation disguised as simplicity.
How We Got Here
Early React (circa 2014) was all about component-local state. The mantra was “UI = f(state)”, and setState was your tool of choice. Derived state — values computed from other values — wasn’t a first-class concept.
When Hooks arrived in 2019, useState gave us fine-grained control, but it didn’t change that mindset. We just traded this.state for more useState calls. The community normalized patterns where every little toggle or form field got its own hook.
Redux tried to fix this by centralizing state and introducing selectors (a form of derived state). But Redux was too heavy for many apps, so developers fled back to local state — without bringing along the lessons about data relationships.
That’s the legacy Ryan is calling out.
The Shift Toward Cohesive Models
Ryan’s critique isn’t a teardown of React — it’s a reminder that data should reflect relationships, not just variables. The healthier model is something like:
const [form, setForm] = useState({ firstName: '', lastName: '', email: '' });
const isValid = useMemo(() => validate(form), [form]);
Here, form is the single source of truth. isValid isn’t stored — it’s derived. When the form changes, validity recalculates automatically. No extra useEffect, no sync headaches.
This small shift — from discrete states to cohesive data — can completely change how you reason about your code.
Lessons from the Community
Scrolling through the replies to Ryan’s post, I saw a pattern. Developers who’d reduced their useEffect usage also found they needed fewer useState hooks. The two problems were feeding each other.
Another interesting thread suggested using the URL as the default state container — a form of natural constraint. You can’t mutate it casually, and it forces you to think in terms of serializable, shareable state. It’s clever, but Ryan pointed out it doesn’t solve the modeling issue; it just moves it around.
What struck me most was the agreement that modeling is the real skill gap. State management libraries (Zustand, Jotai, Recoil) have evolved to support derived state and selective subscriptions, but they can’t fix a bad model. You still need to understand your data relationships first.
My Takeaways
I refactored a component recently that had seven separate useState calls and four useEffect syncs. It now has one state object and one useMemo. The code reads top to bottom without surprises. No ghost dependencies. No hidden chains.
This is the kind of shift Ryan’s tweet is pushing us toward — not a new library, but a new mindset.
-
Model state as a system, not fragments. Start with data structures that mirror your domain. Derived values should be computed, not stored.
-
Reach for useReducer when updates get complex. It’s not about over-engineering — it’s about keeping logic explicit.
-
Use useState for truly independent toggles only. Most of your component data probably doesn’t fit that definition.
-
If your code feels “effect-heavy,” step back. Chances are you’re using effects to synchronize what should be inherently connected.
The Road Ahead
SolidJS, Svelte, and Vue have built-in reactivity models where derived state is the default, not an afterthought. React isn’t there yet — though the Signals proposal hints it might be coming. Whether React 19 or 20 makes that shift remains to be seen.
But we don’t need to wait for the framework to evolve. Ryan’s reminder is practical: stop patching your model with useEffect, and start modeling your data as relationships instead of variables.
That one mental flip — from “states” to “systems” — is where cleaner React code begins.