A shop's React app already uses Apollo Client 4 for server data; when would you keep its cart drawer flag, theme and isInCart in Apollo rather than a separate state library?
answer
- split the state by kind
- decorates a server entity or not
- one query, one source
- bare functions, no structure
- complex workflows want a store
basics
~20 sKeep state in Apollo when it decorates server entities or a query must read it, like isInCart. Small UI flags like the drawer or theme can be reactive variables, but interrelated client state with many writers belongs in a dedicated store.
solid answer
~50 sI would split the state by kind rather than pick one home for all of it. `isInCart` is derived from a server `Product` plus local cart ids, so a type-policy `read` function on `Product` is the strongest case for Apollo: one query returns it next to `name` and `price`, and it updates when the cart variable changes. The drawer flag and theme are app-wide UI state with nothing to do with GraphQL; a reactive variable read through `useReactiveVar` is fine, and so is React context. I would move to a dedicated store when client state grows into interrelated workflows with many writers, such as a multi-step checkout with undo, where actions, middleware and structured tests pay off. The cost of Apollo for that is real: reactive variables are bare functions, usually module-level, with no structure or built-in persistence, and in Apollo Client 4 `@client` needs `LocalState` configured.
go deeper
Know that Apollo can hold client-only state through reactive variables and @client fields, and that teams still sometimes add a separate store.
Be able to say which state benefits from being a @client field, such as a value derived from a server entity, and which does not.
Name concrete costs on both sides: unstructured global variables and reset behaviour in Apollo, duplicated sources and copied server data with a store.
Propose a written team rule that splits state by kind, and describe the signals that would make you revisit it as the codebase grows.
## The question behind the question Apollo Client can hold client-only state, so a team that already uses it for server data will ask whether it needs another state library at all. There is no single right answer. The useful move is to stop treating "client state" as one thing and sort it by what it is attached to and who changes it. ## Sorting the shop's state | State | Attached to | Natural home in Apollo | Case for Apollo | |---|---|---|---| | `isInCart` on `Product` | a server entity plus local cart ids | `read` function on `Product.isInCart` | strong: read in the same query as server fields | | cart drawer open flag | nothing on the server | reactive variable, `useReactiveVar` | neutral: React state or context works as well | | theme | nothing on the server, persisted by the app | reactive variable initialised from storage | neutral to weak: persistence is hand-written either way | | multi-step checkout form | a workflow with validation and undo | would be reactive variables or cache writes | weak: needs structure Apollo does not provide | ## When Apollo is the better home - **The value decorates a server entity.** A `read` function on `Product.isInCart` makes the value available wherever a query selects that field, on the product page, in search results, in recommendations, without each component combining two sources. - **A query needs it.** State held in Apollo can back a `@client` field or feed a remote argument through `@export`. A value in another store is usually passed in as a variable by hand, and changes to it do not recompute local fields. - **There is little of it.** A handful of flags does not justify a second library, its provider and its conventions. - **One mental model helps the team.** Components read server data and the local fields that decorate it through the same `useQuery` call. ## When a dedicated store is the better home - **Many writers, related updates.** A checkout with steps, validation, undo and draft saving is a small state machine. A store with named actions, reducers or explicit setters, and middleware for logging, makes those changes traceable. Reactive variables are usually module-level functions that any importing module can call with any value. - **Behaviour must be testable in isolation.** Pure reducers or store slices are easy to test without a client, a cache or GraphQL documents. - **State must outlive or ignore the GraphQL client.** Resetting the cache on logout does not reset reactive variables, and leaving Apollo later would take any state stored in it along. - **Persistence and hydration matter.** Apollo has no built-in persistence for reactive variables; store libraries often ship a middleware for it. ## Costs on each side Keeping everything in Apollo: 1. Local state in Apollo Client 4 is opt-in, so `@client` fields need `localState: new LocalState()` and, for resolvers, a second place to look for logic. 2. `read` functions must be synchronous, and resolver results are cached like server data, which surprises people who expect them to recompute. 3. The client-state design becomes coupled to the GraphQL client's API and its upgrade cycle. Adding a store: 1. There are now two sources of truth to explain to every new team member. 2. Values that combine server and client data, like `isInCart`, are computed in components or selectors from two hooks instead of appearing as a field on the entity. 3. The temptation to copy server data into the store appears, and with it the synchronization bugs a normalized cache exists to prevent. ## A defensible rule for the team 1. Server data and anything derived from a server entity lives in Apollo, the derived part as `read` functions. 2. Small, global UI flags may use reactive variables or React state; pick one convention and write it down. 3. Client-side workflows with several writers go into a dedicated store, which never holds copies of server entities. 4. Revisit the rule when the number of reactive variables or cross-variable updates starts to grow. The rule matters more than which library wins: the failure mode in real codebases is not choosing the wrong tool once but having the same kind of state live in three places because nobody decided. In an interview, the strongest answer names the split, gives the shop's examples on each side, and admits the cost of whichever option is chosen.
- Your team moved the cart ids into a separate store but still wants isInCart in product queries. What are the options?A `read` function only tracks Apollo's reactive variables and cached fields, so changes in another store never trigger it to recompute. Either mirror the ids into a reactive variable on every store change and keep the `read` function, or drop the local field and compute `isInCart` in components or selectors from both hooks. The first keeps one query; the second keeps one source of truth.
- How do you stop a user's local state leaking into the next session after logout?Resetting Apollo's cache clears cached query results, including local fields written with `writeQuery`, but not reactive variables, which keep their last values. A logout routine must reset each variable to its initial value explicitly, and do the same for any separate store, before the next user signs in.
saying these in an interview costs you the question
- Apollo's local state makes a dedicated state library pointless in any Apollo app.
- Copying server entities into a separate store keeps them in sync automatically.
- Every piece of UI state should become a @client field for consistency.
- Reactive variables give actions, middleware and persistence like a store does.
- Resetting Apollo's cache on logout also resets every reactive variable.