Sharing state between components - discussion (2023-04-29)
The "sharing state" article is one of those pieces that sounds like a beginner topic until you realize how many production bugs are just "two surfaces disagree about truth." I'm curious what patterns people use to share state without turning the whole app into a single global blob (and without hiding truth in custom hooks that nobody can audit).
When you lift state, do you also lift lane/evidence (pending/ok/error) or keep posture local? Do you prefer passing a single contract object (deep cargo) vs passing multiple props for each field? Where do you draw the boundary between "shared state" and "shared derived view model"? How do you keep shared state narratable in logs and visible as UI evidence?
Comments (18)
Back to latestWe share derived view models more than raw state.
Raw shared state tends to become everyone’s dependency.
We lift lane + evidence with the data, otherwise you get "the value is shared but the posture isn't" bugs.
txt
[tips] contract=SearchShared lane=pending reason=query:commit
[tips] contract=SearchShared lane=ok reason=fetch:resolve evidence=lane:ok
Then every consumer can render the same story and support can screenshot the evidence chip.
Deep cargo beats prop soup for shared state, but only if the cargo contract is stable and owned.
If everyone adds keys whenever they need something, cargo becomes the new global store.
Concrete alternative: don't lift; duplicate and reconcile later.
It can work for performance (local fast state), but you need an explicit drift policy. If drift is "we'll just fix it" it never gets fixed.
Yeah, the drift-policy part is where teams fall over.
If you duplicate state, you should also duplicate the narration: who wins and why.
Long-form: the two real failure modes are (1) shared mutable state and (2) shared implicit meaning.
Even if the state value is shared correctly, different components interpret it differently and you get divergence.
We solved it by introducing a small contract type and making each consumer render a common evidence token so interpretation stays aligned.
Docs tie-in: deep cargo + contract layering makes this feel less like "state management" and more like composition discipline.
Related: Components and Composition and Core Concepts.
We try to share state only at route boundaries. Inside a route, we lift state; across routes, we treat it as a doc/vault with identity.
Otherwise you end up with state that outlives the UI that created it.
Short take: if two components need the same state, the question is who owns the story.
I like sharing derived view model hooks, but I insist the hook returns an explicit lane + evidence.
Without that, the hook becomes a black box and every consumer invents its own loading story.
A tiny pattern: shared state should have a stable identity key, even if it's just a string.
The moment identity is implicit, caching and testing get weird.
Long-form counterpoint: lifting state can become a performance trap if you lift too high.
We had a list + detail view. We lifted selection to the parent and suddenly every scroll rerendered everything. The fix was sharing selection as a stable contract, but keeping transient UI state local.
We made shared state auditable by rendering a compact evidence line in dev builds:
txt
[tips] evidence shared=SearchShared lane=ok source=cache identity=q:react
It sounds like a lot, but it stopped the "why is this stale" Slack threads.
One rule that helped: shared state must be serializable (or at least explainable).
If you can't print it in an evidence chip, it's too magical to share.
Takeaway: sharing state works when you're sharing a contract, not a bag of mutable variables.
The contract includes posture (lane) and observation (evidence), otherwise it's not shareable truth.
Also: lift less than you think. Share only what multiple surfaces truly need.
If you want to keep this sane, decide which things are draft and which things are committed.
Draft can be local; committed can be shared. Mixing them is where a lot of bugs come from.
I like sharing state more when the UI can show the lane and the source.
Otherwise you can't tell if a shared value is stale, cached, pending, or wrong.
Good thread. The article is simple, but the boundary discipline is the real work.