How to think about state in any UI framework (without going crazy)
Master UI state management with practical, modern principles that improve app reliability, performance, and developer clarity across React, Vue, Svelte and more.
State management is not hard because libraries are complicated.
State management is difficult because most developers never build a proper mental model of where data lives, who owns it, and how it moves. Libraries simply expose that confusion quickly.
You’ve seen it happen.
A simple toggle becomes a store.
The store grows tentacles.
Suddenly the profile picture disappears when you update the shopping cart.
Someone says, “We should rewrite this in the new framework.”
No.
The framework was not the problem.
The mental model was broken from day one.
This article is not a tutorial.
It’s a corrective lens.
By the end, you’ll understand state in a way that persists:
- The React compiler and server components
- Signals and micro-reactions
- Server actions and URL-first applications
- Any future frameworks that haven’t been invented yet
If you’re looking for “how to use the X library,” leave now.
If you want to stop fighting the UI state forever, keep reading.
1. What a state really is (not what the tutorials say)
Most definitions of a state are uselessly vague.
“State is data that changes over time.”
True – and not helpful.
Here’s the definition that’s really important:
State is the minimum set of data needed to recreate the UI at any given moment.
If I take a snapshot of your state and reload the app from that snapshot, the UI will reappear exactly as it was. Same open menus. Same preferences. Same logged-in user. Same loading indicators.
If it doesn’t – your state model is incomplete or duplicated.
That’s it. Same core idea.
Everything else – hooks, signals, stores, atoms – is just machinery around that definition.
2. UI is a pure working state
In every modern UI system in 2026 – React, Solid, Svelte, Vue, Qwik, SwiftUI, Jetpack Compose – the winning architecture is the same:
UI = f(state)
No exceptions.
If you manipulate the DOM directly, manually transform visual widgets, or let components “remember” things outside of state – you are lying to your future self.
If you ever find yourself doing this:
- “Let me hide this div manually”
- “Let me query the DOM for the current value”
- “Let me set a class forcibly”
You’re patching cracks in concrete that’s already failing.
Correct mental model:
- State changes.
- Framework re-renders.
- UI reflects truth.
Anything else is technical debt with interest.

3. Why State Seems Difficult in Real Projects
State seems difficult because data relationships grow faster than features.
First week:
- One toggle.
Month two:
- The toggle relies on authentication.
- Authentication relies on token refresh.
- Token refresh relies on background timer.
- Timer triggers UI notifications.
- Notifications appear in a separate layout branch.
None of this is hard on its own.
What kills projects is unclear ownership.
When no one can answer:
- “Where does this data live?”
- “Who is allowed to change it?”
- “Who uses it?”
You get zombie bugs.
Not because the code is complex.
Because responsibility is undefined.
4. Three Toxic Mental Models
1) Global Bucket
“Two components might need it someday, so let’s put it in the global store.”
That’s not architecture.
That’s hoarding.
Result:
- Every update triggers unnecessary reactivity.
- Components become tightly connected to the global context.
- Reuse becomes impossible.
- Debugging becomes an archaism.
Global stores are not the default storage.
They are shared infrastructure as a last resort.
2) UI-As-Truth Model
Old jQuery thinking still infects modern applications:
- Read input value from DOM.
- Set other UI based on that.
- Manually synchronize components.
That produces an out-of-sync state. Always.
In the correct architecture:
- The input field does not “hold” data.
- State holds data.
- The input reflects that.
The DOM is a projection.
Never a source.
3) The “Effects will sync everything” model
In React before the compiler era, developers abused useEffect to glue state together:
“If X changes, update Y.”
This created:
- Invisible update chains
- Race conditions
- Impossible debugging
Modern frameworks (React Compiler, Signals, Solid, Svelte) have made this mistake harder to make – but the mental habit remains.
The correct model:
Events cause state transitions.
State transitions cause UI updates.
Not the other way around.
5. State Categories That Really Matter
Not “local vs. global”. It’s that simple.
In real architecture, the state falls into five functional categories.
1) Transient local UI state
Examples:
- Is the dropdown open?
- Howard Item id
- Temporary input text
Rules:
- Lives inside the component
- Dies when the component is unmounted
- Never shared
If the component disappears and nothing breaks – it’s local. There is no discussion.
2) Shared feature state
Examples:
- Multi-step form data
- Wizard progression
- Product filter preferences
Rules:
- Resides on nearest common ancestor
- Passed or injected through feature-level store
- Not in global app store
If only one feature cares – then it is feature state, not global state.
3) Global App State
Examples:
- Authentication Session
- Theme Selection
- Locale / Language
- Global Toasts
Rules:
- Rare
- Carefully Scoped
- Explicitly Versioned
If more than ~20% of your state is global – your architecture is lazy.
4) Server State
In 2026, this is the biggest change.
Data retrieved from servers:
- User profiles
- Feeds
- Products
- Dashboards
This is not UI state.
This is remote cache state.
These modern libraries (React Server Components, TenStack Query, tRPC, Relay, SWR, SvelteKit Loaders) treat server state differently – because it follows different rules:
- Requires caching
- Requires invalidation
- Requires background revalidation
Mixing server state into UI state is still the #1 cause of bloated stores.
5) URL state
If it should be saved on a page refresh – it’s in the URL.
Examples:
- Filters
- Sorting
- Pagination
- Selected Item
If it affects what the user sees and should survive a refresh – put it in query parameters, not in memory state.
This one rule eliminates half the unnecessary global stores.
6. Derived state is not state
Let’s be aggressive here:
If you store something that can be counted – you are writing a bug into the future.
Common mistakes:
- Separately storing item count from list
- Separately storing “isValid” from form fields
- Separately storing “isLoading” from request state
Correct:
- Calculate count from list length
- Validate count from schema
- Loading count from request state
Derived values are pure functions.
They are never in stores.
Every duplicate source of truth eventually guarantees desynchronization.
7. Ownership: The Single Source of Truth Principle
Every piece of state should have:
- One owner
- Many clients
- Zero hidden authors
A child never directly modifies a parent state.
The parent passes data and event handlers.
Events go up. Data goes down.
This is true:
- React Props
- View Emit
- Svelte Dispatch
- Solid Signals
- SwiftUI Bindings
Different syntax. Same law.
Break this rule and debugging becomes possible.
8. State Transitions, Not State Mutations
Modern 2026 architectures favor explicit transitions:
Instead of:
- setLoading(true)
- setData(res)
- setLoading(false)
You define:
- status: “idle” | “loading” | “success” | “error”
and display:
- transition(“success”, payload)
This eliminates impossible intermediate states.
State machines (XState, Svelte machines, custom reducers) did not become popular by accident. They reflect how real systems behave.
If your UI can ever reach an impossible combination of values – then your model is wrong.
9. Side Effects Are Orchestrators, Not Owners
Side Effects:
- Fetch Calls
- Timers
- LocalStorage Writes
- Analytics Events
They are not owners of state.
They trigger transitions.
If the fetch callback mutates five different state variables – your architecture is already broken.
Correct pattern:
- Effect runs
- Emits event
- Updates single transition state atomically
One cause. One change. One re-render.
10. 2026 Reality Check: Modern Framework Behavior
Let’s ground this in current ecosystem reality.
React in 2026
- The React compiler eliminates most manual memoization.
- Server components separate server state from client state.
- Server actions eliminate many client-side mutation stores.
- useEffect is now a rare escape hatch.
If your mental model still revolves around “sinking state with effects”, you’re writing 2022-era React.
Signals (Solid, Vue, Angular Signals)
Signals implement fine-grained reactivity:
- Only clients rerun.
- No global re-renders.
- No dependency arrays.
But they don’t fix bad ownership.
You can still create a global bucket – just faster.
The same rules still apply.
Svelte / Qwik / Astro
These frameworks do more work on the server:
- Less client state
- More URL-driven state
- More server loaders
If your app breaks on refresh – you’re doing it wrong.
Universal Truth
Every modern UI trend points to:
- Less client state
- More server state
- More URL state
- Fewer global stores
- Fewer side effects
- Purer derivation
Frameworks changed.
That didn’t happen in a true mental model.
11. Common Failure Patterns (and Why They Fail)
Prop Drilling
Symptom:
Passing data through components that don’t use it.
Reason:
State is placed too high.
Fix:
Create a feature-level context or store — not global, not root.
State Mirroring
Feature:
Copying props to local state.
Reason:
Afraid of mutating props.
Fix:
Use props directly. Store drafts separately if necessary.
Effect Chains
Symptom:
“If A changes, effect updates B, which triggers effect update C…”
Cause:
Trying to get state by effect.
Fix:
Move logic into an event or reducer.
Global Store Everything
Features:
Auth, Cart, Filters, Form Drafts, Models, Toasts – all in one store.
Reason:
There is no state classification.
Improvement:
Separate UI, Server, Feature and URL status.
12. A Practical Decision Framework
When adding any data, ask:
- If I refresh the page, should this persist?
→ Yes: URL or server
→ No: Memory state - Can I calculate this from existing data?
→ Yes: Obtained - Does more than one unrelated feature need it?
→ Yes: Global - Does only one feature need it?
→ Yes: Feature-level shared state - Does only one component need it?
→ Yes: Local
If you follow this honestly, your architecture will be boring.
Boring is good. Boring scales.
13. Why Rewrites Happen (and How to Avoid Them)
Most rewrites blamed on “framework limitations” are actually:
- Global bucket state
- Duplicated truth
- Obscure ownership
- Effect-driven synchronization
Some new library fixes it.
Only true mental models do.
14. The sole goal of state architecture
Make impossible states impossible.
If your UI can ever display:
- Logged out but profile visible
- Empty cart but checkout enabled
- Form valid but fields empty
Your model allows impossible states.
Fix the model. Not the features.
15. The final reality check
The framework changes every two years.
Mental models last your entire career.
If you understand:
- Single source of truth
- Ownership
- Derived vs. stored data
- One-way data flow
- Atomic transitions
You’ll never be afraid of a new UI framework again.
You’ll read the documentation one afternoon and say:
“Oh. Same principles. Different syntax.”
That’s senior-level thinking.
Frequently Asked Questions
Q: Do I still need Redux or a global store in 2026?
A: Sometimes. Mostly for:
1) Auth Session
2) Theme
3) Cross-App Notifications
If your store has filters, form drafts, request cache, UI toggles – you’re overusing them.
Q: Are signals better than React state?
A: Signals are a primitive reactivity. They solve re-render performance. They don’t solve architecture. Bad mental models stay bad.
Q: Should I keep the server data in UI state?
A: No. Use dedicated server-state tools (RSC, loaders, query libraries). Treat server data as cached remote state, not UI truth.
Q: Where should the loading states be located?
A: Inside request state machines or server-state libraries. Not as a random boolean.
Q: Is useEffect dead?
A: Not dead. Demoted. If you need it for core data flow, your architecture is probably wrong.
Q: How much global status is too much?
A: If more than ~20% of your state is global, you’re compensating for unclear ownership.
Q: Should I store form state globally?
A: Almost never. Forms are local. Globalizing them is an architectural faux pas.
Q: What about LocalStorage?
A: Persistence layer. Not state layer. Sync explicitly across boundaries.
Q: How can I quickly debug state bugs?
A: Ask:
1) Who owns this data?
2) Who has permission to change it?
3) Can this value be obtained instead?
If you can’t answer in 10 seconds – architecture problem.
Q: Will AI-generated code fix state architecture?
A: No. AI writes bad architectures quickly too. Mental models remain a human responsibility.
Closing
Stop chasing libraries.
Stop cargo-culling patterns.
Stop assuming that state is “just a thing to react to”.
State is data ownership and data flow.
Always has been. Always will be.
Once you really internalize it, the framework stops seeming difficult.
And your applications stop crashing after six months.
