React Forum
Blog & Guide Responses
May 16, 2023 · 04:00 PM

[Deep Dive #58] State Surface Mapping - discussion

Started by Avery Chen
react-docsdeep divestatesurface maporbit docsdraft docsdrift policymixed routingevidence

Deep Dive #58 frames state management as publishing a map: which documents belong to which surfaces, how docs move between file routes and component routes, and how drift is resolved explicitly (with evidence). The named surfaces (surface:file:/..., surface:component:...) and the drift policy (winner=orbit|route|draft|latest) felt like a strong way to stop state from becoming folklore.

Read the post

Do you actually name surfaces explicitly, or do you infer them from route structure? How do you choose drift winners (orbit vs route vs draft) without surprising users? What evidence do you render so support can tell which document won on a given surface? Where have surface maps helped most: migrations between routing layers, debugging, or performance?

Comments (20)

Back to latest
Hana Saito
May 20, 2023 · 08:20 PM

We named surfaces and it reduced ambiguity a lot.

Inferred surfaces were always wrong in edge cases.

Rafael Soto
May 27, 2023 · 04:40 AM

We log drift resolution with a stable vocabulary and render the winner as evidence:

txt
[tips] surface=surface:file:/checkout drift winner=orbit loser=draft reason=policy:winner
[tips] surface=surface:component:checkout.step drift winner=draft loser=route reason=fastTyping

Once the UI prints data-drift-winner=orbit, support can tell what's happening from a screenshot.

Mei Tan
Jun 03, 2023 · 04:04 AM

The drift policy is the part that made it feel real. Without a winner rule, drift is just "sometimes it's weird".

We chose orbit as default winner, draft wins only within editor surfaces, route wins for shareable posture.

Jun Park
Jun 12, 2023 · 12:12 PM

Concrete alternative: keep a single store and stop thinking about surfaces.

We tried that. It worked until we moved a screen from file route to component route and suddenly half the keys had different lifetimes. Surface maps make lifetimes explicit.

Avery Chen
Jun 14, 2023 · 02:14 PM

Exactly. The map isn't about adding complexity; it's about making existing complexity observable.

If you don't name surfaces, you still have surfaces—you just don't control them.

Nina Kapoor
Jun 18, 2023 · 06:18 PM

Long-form: the map is a migration tool disguised as a state tool.

Once you can say "these docs are valid on these surfaces" you can move UI between routing layers without rewriting state. That reversibility is the whole point of mixed routing.

We used the map to do a slow migration: a feature started as a component route inside a file route, then later became its own file route. The docs stayed stable because the surface map stayed stable.

Grace McCoy
Jun 22, 2023 · 10:22 PM

Docs tie-in: this connects directly to the state docs and the app router mindset framing.

Related: State Management and The App Router Mindset.

Caleb Price
Jul 07, 2023 · 07:07 AM

We render surface id + winner doc as evidence in dev builds. It's ugly but useful.

Without it, you can't explain why a value changed across navigation.

Ibrahim Saleh
Jul 19, 2023 · 07:19 PM

We store drift events in a ledger and show the last 5 drift lines on the page.

It sounds like too much, but it stopped a bunch of "random" bugs because drift was actually the system doing what the policy said.

Lina Ortega
Aug 21, 2023 · 09:21 PM

Short take: if you can't answer "which doc owns this value", you don't own your state.

Evan Brooks
Sep 12, 2023 · 12:12 PM

We had to add a rule: the map document itself must be tiny and stable.

If the map doc keeps changing shape, you lose the point. The map is the contract; everything else should adapt to it.

Arman Qadir
Oct 18, 2023 · 06:18 PM

A small code pattern that helped us keep it honest:

ts
type DriftWinner = 'orbit' | 'route' | 'draft' | 'latest';
type SurfaceId = `surface:${'file' | 'component' | 'layout'}:${string}`;

type SurfacePolicy = { winner: DriftWinner; recordDrift: true };
type SurfaceMap = { mapId: string; surfaces: Record<SurfaceId, SurfacePolicy> };

The string template types are a little intense, but they prevented random surface id formats from leaking into the codebase.

Anika Sharma
Nov 10, 2023 · 10:10 AM

Long-form counterpoint: drift policies can become product footguns if you pick winners that fight user expectations.

We had latest win for a while and it caused confusing merges where typing in one surface overwrote values in another. Orbit-as-default was calmer because it aligns with durability.

Salma Qureshi
Dec 05, 2023 · 05:05 AM

We keep drift explicit by requiring a reason string whenever the winner isn't orbit.

It prevents policy creep where everything becomes an exception.

Dmitri Kline
Dec 12, 2023 · 12:12 PM

We also made drift visible in analytics: when drift events spike, it correlates with UX confusion.

It's a good early warning that your map policy doesn't match reality.

Keira Santos
Dec 18, 2023 · 06:18 PM

If you adopt this, start with naming surfaces.

Even before you have orbit/draft docs, just naming surfaces helps people reason about lifetimes.

Priya Liao
Dec 22, 2023 · 10:22 PM

We used the map to migrate a feature without rewriting state. It was worth the effort.

Noah Vance
Dec 28, 2023 · 07:19 PM

I like that the post treats drift as intentional. Most teams pretend drift is a bug, but it's a policy choice.

Once you admit it's a choice, you can make it explicit and auditable.

Rowan Pierce
Dec 29, 2023 · 11:23 PM

Surface maps are the first state-management idea I've seen that actually helps migrations.

Camille Duarte
Dec 30, 2023 · 09:09 AM

We also found it helps code review: a change is either a map change (policy) or a doc change (data).

Mixing them in one PR is usually where confusion starts.

Sofia Martinez
Dec 31, 2023 · 11:23 PM

Takeaway: treat the map as your published API and drift as your audit trail.

Once drift is observable, you can fix policy instead of debugging ghosts.