Back to React
React
medium
mid

What is your strategy for managing global state efficiently in a React app with multiple teams contributing?

Separate kinds of state first: server state → React Query; local UI → useState; cross-tree config (theme, auth) → Context; cross-tree app state (cart, editor) → Zustand or Redux Toolkit with selector subscriptions; URL state → router. With multiple teams: shared store package, feature-scoped slices, RFCs for additions, store namespacing to prevent collisions, devtools, and clear ownership.

5 min read·~12 min to think through

Step 1 — categorize

KindTool
Server stateReact Query / SWR / RTK Query
Local UI stateuseState
Cross-tree config (theme, auth user)Context
Cross-tree app stateZustand or Redux
URL staterouter
Form stateReact Hook Form + Zod

Putting server state in a client store is the #1 anti-pattern at scale — see [[compare-trade-offs-in-state-management-redux-zustand-context]].

Step 2 — choose by characteristics

  • Frequent updates (per keystroke, cursor pos) → Zustand or Redux (selector subscriptions). Avoid Context for these.
  • Coordination across many teams → Redux Toolkit's slice pattern + devtools + middleware ecosystem maps to bigger teams better than ad hoc stores.
  • Small to medium → Zustand. Lower ceremony, easier to evolve.

Step 3 — multi-team conventions

Multiple teams sharing a store needs governance:

  1. Feature-scoped slicescart, checkout, search, each owned by a team.
  2. Slice file conventionfeatures/cart/slice.ts exporting state + actions + selectors.
  3. Selectors > raw state access — prevents teams from reading shape they shouldn't.
  4. Cross-slice rules — slice A can't read slice B's state directly; use exported selectors or events.
  5. Action namespacingcart/itemAdded, not itemAdded.
  6. Migration policy — slices evolve their internal shape behind selectors.
  7. RFCs for new top-level slices — keeps the store from sprawling.
  8. Codeowners — feature team owns its slice's PRs.

Step 4 — performance hygiene

  • Selector subscriptions — components subscribe to slice they care about (Zustand selector / RTK useSelector).
  • Shallow / custom equality — pass shallow to selector for object slices.
  • Avoid returning new object references from selectors per call.
  • Memoize derived selectors (reselect).
  • Split high-frequency UI state out of the global store — useState locally instead.

Step 5 — server state separation

Don't mirror server data in the global store. React Query / SWR / RTK Query own:

  • Caching with TTL + revalidation.
  • Mutation + invalidation.
  • Background refetch on focus.
  • Request dedup.
  • Optimistic updates.

This eliminates 80% of what people historically put in Redux.

Step 6 — persistence + SSR

  • Persist middleware (zustand/persist, redux-persist) for offline / draft data.
  • Hydration: avoid mismatches by gating client-only state until mount or by streaming from server.
  • SSR: store created per request to prevent cross-user leak.

Step 7 — observability

  • Redux DevTools (Zustand has a devtools middleware).
  • Action logs in dev.
  • Sentry breadcrumbs for actions before errors.

Step 8 — DI for testability

Inject the store / API client. Don't import singletons everywhere. Makes Storybook + tests trivial.

Anti-patterns

  • "One big context" → re-renders fan out.
  • Server state in Redux/Zustand.
  • Slices reading each other's internal shape directly.
  • Every team adding a top-level store without governance.
  • Global state for what's really local UI (modal open, hover).

Interview framing

"Categorize first: server state → React Query, local UI → useState, cross-tree config → Context, cross-tree app state → Zustand or Redux Toolkit with selector subscriptions, URL state → router. With multiple teams I'd land on RTK with feature-scoped slices (cart/, checkout/), strict selector + action conventions, namespaced action types, codeowners per slice, and RFCs for adding top-level slices. React Query handles server state — never mirror it in the client store. Hygiene: selectors with shallow equality for object slices, memoize derived selectors, avoid context for hot state. Devtools + breadcrumbs + per-request store on SSR."

Follow-up questions

  • Why Zustand vs RTK for multi-team apps?
  • How do you prevent slices from reading each other's shape?
  • Walk through how React Query fits in.

Common mistakes

  • Server state in client store.
  • Context for hot state.
  • Slices coupled to each other.
  • No selector convention.

Performance considerations

  • Selectors with custom equality cut re-renders. Hot UI state stays local, not global.

Edge cases

  • Cross-slice events / dependencies.
  • Persistence + hydration mismatches.
  • SSR store-per-request safety.

Real-world examples

  • Linear's editor store, Figma's selection state, large fintech apps using RTK slices per feature.

Senior engineer discussion

Seniors anchor on the server-vs-client distinction, design slice ownership for the org, and reach for selector subscriptions + RTK for the multi-team scenario.

Related questions