Skip to content

Split-State Account Settings

This example is about ownership boundaries, not fancy form plumbing.

Three sections own different parts of the same account settings store:

  • ProfileSection owns email and displayName
  • PlanSection owns plan
  • TeamSection owns teamSize and teamDomain

Umpire still evaluates cross-cutting rules once, globally:

  • teamSize only enables when plan === 'team'
  • teamDomain requires teamSize > 0
  • email stays required the whole time

select() is the whole trick:

const umpStore = fromStore(accountUmp, store, {
select: (state) => ({
email: state.profile.email,
displayName: state.profile.displayName,
teamSize: state.team.size,
teamDomain: state.team.domain,
}),
conditions: (state) => ({
plan: state.billing.plan,
}),
})

Each section keeps owning its own slice. Umpire never needs to know that the values came from different components.

  1. One fromStore() call sets up the whole app-wide availability layer.
  2. requires() still propagates transitively, so disabling teamSize also knocks out teamDomain.
  3. Any component can read umpStore.field('teamSize') without mounting its own useUmpire() instance.
  4. When the plan flips back to personal, fouls recommend clearing stale team values instead of mutating state automatically.
  • Use @umpire/store if your store already provides (next, prev).
  • Use @umpire/zustand if you want the named Zustand entry point.
  • Use @umpire/redux, @umpire/pinia, @umpire/tanstack-store, or @umpire/vuex when those libraries need the thin normalization layer.