In React, what makes data fetched from an API different from client state such as whether a dropdown is open, and why does that difference change how you store each?
answer
- who is allowed to change this value
- one is owned, one is borrowed
- a copy of something that lives elsewhere
- cache: stale, shared, keyed, revalidated
- isOpen never needs refetching
basics
~20 sFetched API data is a local cache of state the server owns: shared with other users, able to go stale, needing refetch and invalidation. Client state such as an open dropdown is owned entirely by this browser tab and is never stale.
solid answer
~50 sClient state is anything this browser tab alone decides — is the dropdown open, which wizard step am I on, what has the user typed but not submitted. The component that owns it is the source of truth, so a `useState` next to that component is the whole story. Data fetched from an API is different in kind: the source of truth is a database somewhere else, other people can change it after I read it, and what I am holding is a *cache* of it. That means it arrives asynchronously, it can be wrong the moment after it lands, it needs an identity (a key like `user/42`) so two components asking for the same thing share one copy, and it needs a rule for when to refetch or invalidate — especially after my own writes. Treating server data as if it were plain local state is where most of the bugs come from.
code
javascript · 7 lines// client state: this tab is the source of truth
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
setIsSidebarOpen(true); // now unconditionally true
// server state: the setter changes a *copy*, not the truth
const [profile, setProfile] = useState(null);
setProfile({ ...profile, name: 'Ada' }); // server may reject, normalize, or already differgo deeper
Be ready to sort a screen's values into two buckets out loud and justify each: data that came from an API versus data only this tab decides. Say plainly that fetched data is a copy that can go out of date.
Explain the properties that make fetched data a cache — staleness, an identity shared across components, loading and error states, invalidation after writes — and show which of them a plain useState gives you for free (none).
Show the failure reports this distinction predicts: stale headers after an edit, duplicate requests from sibling panels, deleted rows that linger. Argue for a single keyed copy of each entity and a deliberate revalidation policy per data type.
Own the boundary as an architectural rule for the codebase: which layer holds cached server data, which holds client state, and what the review rule is for keeping them apart. Say what you would standardize so teams stop hand-rolling the cache lifecycle screen by screen.
## Two kinds of state on every screen Every value a React screen renders comes from one of two places. Either this browser tab is the only thing that decides it — whether a menu is open, which row is selected, the current step of a wizard, text typed into a search box but not submitted — or it is a copy of something that lives on a server, where other users, other tabs, background jobs and admins can change it without telling you. The first kind is **client state**. The second is **server state**, and the reframing that makes interviewers nod is this: what your component holds is not the data, it is a **cache** of the data. ## Why "cache" is the right word Calling fetched data a cache is not a metaphor; it means four concrete things are true of it that are not true of `isOpen`: - **It is stale by default.** It was accurate at the instant the response was serialized. From then on it is a guess. The only question is how wrong it is allowed to get. - **You are not the owner.** You cannot make a value true by calling a setter. Writing goes through a request that can be rejected, transformed, or applied out of order, and the server's answer is the truth. - **It has an identity.** `user/42` is the same entity no matter which of five components asked for it. Client state has no identity beyond the component instance that holds it. - **It is asynchronous and can be missing.** Every read has at least three outcomes — pending, error, value — so the UI must render all three, whereas `isOpen` is always exactly `true` or `false`. Client state has none of these properties. There is nothing to revalidate, nothing to invalidate, no other writer, no loading state. You set it and read it, and it is correct by definition. ## What that changes in the code A `useState` holding a boolean carries no hidden obligations. A `useState` holding a fetched list quietly signs you up for several: ```jsx function OrderList() { const [orders, setOrders] = useState(null); useEffect(() => { fetch('/api/orders') .then((r) => r.json()) .then(setOrders); }, []); // Implicitly owed, and missing here: // - loading and error rendering // - a key so other components reuse this copy // - refetch after the user creates an order // - a policy for how stale this may get } ``` None of that is pedantry. The bug reports it produces are ordinary: a user edits their name in a settings modal and the header still shows the old one; two panels on the same page each fire the same request; a list keeps showing an item the user deleted in another tab. ## A classification test you can apply out loud Given any value on the screen, ask: **if something outside this tab changed it, would my UI now be wrong?** If yes, it is server state and needs a staleness and invalidation story. If no — nobody else can possibly have an opinion about it — it is client state and belongs in local component state as close to its consumer as possible. By that test, `isSidebarOpen`, `selectedTabId`, `draftMessageText` and `isDragging` are client state. The order list, the current user's profile, the unread-count badge and a product's price are server state. ## The grey areas interviewers like to probe - **Form drafts.** An edit form is seeded from server state but the in-progress draft is client state: it is yours until you submit, and it must not be silently overwritten by a background refresh. - **Values that could be derived.** If a number can be computed from data you already hold, holding it separately just gives you a second thing to keep in sync. - **The URL.** Route params and query strings are a real place to keep client state — shareable, bookmarkable, survives reload — and are often a better home than `useState` for filters and selected ids. ## What the distinction does *not* mean It does not mean "server data must go in a library" — a screen that reads one endpoint once is fine with plain state. And it does not mean "put it in context": context solves *distribution*, not staleness, deduplication or invalidation. The point of the distinction is that server data comes with a lifecycle, and you either implement that lifecycle deliberately or you ship the bugs that come from ignoring it.
- Where would you keep a filter value that the user picks from a dropdown — local state, or somewhere else?It is client state, so local `useState` in the component that owns the filter UI is the default. If the filtered view should be shareable, bookmarkable or survive a reload, promote it to the URL query string instead — that is still client state, just stored where the browser can persist and share it.
- A settings form is seeded from the fetched profile. Is the text in the input server state or client state?The fetched profile is server state; the in-progress draft in the input is client state that was merely seeded from it. Keeping them separate is what lets a background refresh update the cached profile without wiping out what the user is typing, and it makes "discard changes" just dropping the draft.
- Does putting fetched data in context instead of local state fix any of the server-state problems?No. Context distributes a value to a subtree; it has no notion of staleness, keys, deduplication, refetching or invalidation. Lifting fetched data into a provider means one component owns the copy instead of three, which removes duplicate requests, but every other obligation of a cache is still yours to implement.
saying these in an interview costs you the question
- Says server data is just state like any other state
- Thinks a setState call makes the server value true
- Assumes fetched data stays correct until the page reloads
- Calls context a solution to staleness or refetching
- Claims every value on the screen belongs in a global store