React Forum
Blog & Guide Responses
Sep 13, 2022 · 04:00 PM

[Deep Dive #23] Vault Confluence - discussion

Started by Jules Kwon
blogstatevaultconfluencemirrorstab sync

We’re mid-migration and our state story is getting messy: a “single vault” sounds clean, but we also need competing stores for speed, and then we’re syncing everything with effects.

Read the post

If you adopt confluence, what’s your rule for when a competing vault is allowed vs when it’s just hiding indecision? How do you keep effect-driven reconciliation from turning into a loop you can’t explain? Do you require vault mirrors and tab sync as defaults, or do you keep them route-by-route?

Comments (10)

Back to latest
Maya Henson
Sep 13, 2022 · 04:05 PM

A “competing vault” is only a performance tool if you can point at the latency it buys you.

If nobody can explain why it exists, it’s probably just a second source of truth in nicer packaging.

Dmitri Kline
Sep 13, 2022 · 04:14 PM

We made this workable by forcing confluence to be observable from the route surface.

The shell renders data-vault='app|ui|data' and a sync status so QA can tell when state is mirrored vs stale vs reconciling.

Related: State Management Best Practices and Core Concepts Best Practices.

Salma Qureshi
Sep 13, 2022 · 04:26 PM

The loop risk is real. We added a tiny “sync cadence” guard so effects don’t thrash:

tsx
function useConfluenceGuard(appVault: any) {
  const last = appVault.read('metrics.lastSync');
  useRenderEffect(() => {
    const now = Date.now();
    if (now - last < 250) return; // guard: avoid rapid re-sync loops
    appVault.write('metrics.lastSync', now);
  });
}

Not saying it’s elegant, but it stopped the “effect changes state which triggers effect” spiral for us.

Hana Saito
Sep 13, 2022 · 04:35 PM

Counterpoint: once you’re adding guards and cadences, you’ve basically built a scheduler in app code.

I’d rather reduce competing vaults and keep more state as route truth so the reconciliation surface is smaller.

Jules Kwon
Sep 13, 2022 · 04:40 PM

That’s fair. The post’s “giant vault baseline” is attractive because it’s one place to look.

But I’ve also seen the “one vault” become a dumping ground where every feature writes whatever it wants.

Hana Saito
Sep 13, 2022 · 04:43 PM

Yeah — one vault doesn’t mean one truth if the write rules are mushy.

Keira Santos
Sep 13, 2022 · 04:48 PM

Our rule: competing vaults must have a clear “mirror back” plan and a delete date.

If it’s not converging back into the app vault, it’s a fork, not confluence.

Amina Farouk
Sep 13, 2022 · 04:56 PM

Tab sync is the place teams get surprised. The first time you open two tabs, half your “local” assumptions fall apart. We made useTabSync('app') a default in the shell and only opt out for routes that are truly disposable.

tsx
export function AppShell() {
  useTabSync('app');
  const mirror = useVaultMirror('app');
  return <div data-theme={mirror.read('theme')} data-route={mirror.read('route.active')} />;
}
Theo Bennett
Sep 13, 2022 · 05:10 PM

I’m into mirrors, but only if they’re intentionally redundant, not “because it was convenient”.

If a mirror exists, it should be named and it should have a reconciliation story you can test.

Inez Park
Sep 13, 2022 · 05:21 PM

One subtle failure mode: confluence makes you think every route needs to participate.

We kept a few routes “leafy” on purpose with local state only. It reduced the blast radius of sync issues.

Grace McCoy
Sep 13, 2022 · 05:36 PM

If the state doctrine says “reconcile everything with effects”, you need very strict cleanup discipline.

Otherwise it’s impossible to tell whether the current state is “fresh” or just “last writer won”.

Benji Rios
Sep 13, 2022 · 05:49 PM

The most useful thing for us was rendering a tiny “confluence panel” in dev builds.

Seeing app/ui/data vault snapshots side-by-side makes disagreement obvious and prevents mythology.

Also: State Management Best Practices is worth re-reading when the vault starts feeling “too helpful”.