Quiz - Zustand
A teammate wants to move a polled comment thread (refetched every 10 seconds) out of TanStack Query and into a Zustand store, arguing “it’s shared client state that several components read.” What’s the senior objection?
The comment thread is server state — the client doesn’t own it. Caching it in a store means nothing refetches it, so users sit on a stale view. TanStack Query already owns the cache, refetch, and staleness.
It’s fine to move it, as long as the store is per-feature (useCommentsStore) and not part of a global useAppStore.
It’s fine to move it, but only if the store uses the persist middleware so the comments survive a refresh.
persist are real rules, but neither fixes the fundamental error of caching server state in a store.Three buttons in three unrelated subtrees need to open a global confirmation modal. Two of them, however, sit directly under a shared parent component, and only the third is far away. Where does the senior land?
The “imperative action across disjoint subtrees” trigger only fires when more than two unrelated subtrees need it — so this clears the bar; reach for a small store with an open() action.
Lift one callback to the nearest common ancestor of all three buttons and thread it down; a store is never justified for opening a modal.
Two of the three callers share a parent, so lift a callback for those two and leave the third alone — no single mechanism is needed.
A component needs two fields from the wizard store and writes the selector as const { a, b } = useWizardStore((s) => ({ a: s.a, b: s.b })). Why does this re-render on every store change, and what’s the fix?
The selector returns a new object literal each call, and Zustand compares results with Object.is, which always reports a change. Fix it with two atomic selectors, or wrap the object in useShallow.
The selector subscribes to the whole store; you must list the fields in a second argument so Zustand knows which keys to watch.
Object-returning selectors are fine — the re-renders come from missing the 'use client' directive on the component.
Object.is, and a fresh object literal is never === the previous one, so the component re-renders on every store write. The two fixes are atomic selectors (one primitive each) for two or three fields, or useShallow from zustand/react/shallow for a mapped object/array. There’s no second “watched keys” argument, and 'use client' is unrelated.Why does the App Router force createStore plus a per-request provider instead of the simpler module-scoped create<State>()(...)?
A module-scoped store is created once when the module loads and shared across every request the server process handles — so one tenant’s in-progress draft can render into another tenant’s response. A useRef-pinned provider gives each request a fresh store.
create doesn’t support TypeScript generics, so SSR codebases need createStore to type the state correctly.
create re-runs the creator on every render during SSR, which throws away state between steps; createStore memoizes it.
create store is a cross-request data-isolation leak — the same shape as the module-scoped QueryClient. The fix is a fresh store per request, pinned with useRef in a provider. Both create and createStore support generics, and the leak is about shared scope, not re-running the creator.The wizard store resets after submit-success and on sign-out. The lesson insists it must also reset on org-switch. Why is that one not optional?
A draft started under one organization that survives into another is a data-isolation bug at the client cache layer — the same class of failure as the wrong tenant’s rows coming back from a query, just at a different layer.
Org-switch changes the route, which would otherwise leave the wizard mounted on a stale URL.
Each organization has a different Zod schema, so the old draft would fail validation on the new org’s safeParse.
queryClient.clear() discipline from TanStack Query. It’s not about routing or per-org schemas; it’s about not carrying one tenant’s data across a tenant boundary.The wizard deliberately ships no persist middleware, so a hard refresh wipes the draft. Why is “refresh loses the draft” the senior call rather than a missing feature?
A draft that survives refresh is really server state — it would need a customer_drafts table with garbage-collection and tenancy rules, which is product scope, not stack scope. And a half-finished customer silently lingering on a shared computer is a worse surprise than losing it.
persist can’t be used with the per-request provider pattern, so refresh-survival is technically impossible here.
Persisting to sessionStorage would leak the draft across browser tabs, violating tenant isolation.
sessionStorage if product accepts tab durability. Either is a deliberate product decision with real cost; the wizard declines it because a lingering half-finished draft on a shared machine is the worse outcome. persist is compatible with the provider, and sessionStorage is per-tab, not cross-tab.Quiz complete
Score by topic