What does a store that lives outside the component tree expose, and what must a component do to stay in sync with it?
answer
- state with no owning component instance
- three operations, one release function
- subscribe at setup, release at teardown
- re-read after subscribing closes the gap
basics
~20 sA store exposes a read of the current value, a write that changes it, and a subscribe that notifies listeners. A component reads while rendering, subscribes when its instance is set up, re-reads once subscribed, and releases the subscription on teardown.
solid answer
~50 sA store is state that no component instance owns: it lives in its own object, so components in unrelated branches read the same value without anything being threaded through the components between them. Its surface is three operations — a `read` of the current value, a `write` that changes it (often only through named actions), and a `subscribe` that registers a listener and returns a function that releases it. The tree gets no updates for free. A component reads the value while rendering, and separately subscribes when its instance is set up so it learns about later writes. Two details decide correctness: re-read right after subscribing, because a write can land between the first read and the registration, and release on teardown, or the store keeps calling a listener whose component is gone. In a runtime that tracks reads, a store built on its own primitives self-subscribes; a foreign store must be bridged.
go deeper
Recall the three operations and the lifecycle pairing: read to render, subscribe when the instance is set up, release on teardown. Be ready to say why a single read goes stale.
Explain the notification path: a write notifies listeners, each listener re-reads, and the components whose value moved update. Know why the re-read straight after subscribing is part of the contract.
Show failures you have actually fixed: duplicate listeners from subscribing during render, notifications delivered to torn-down instances, and timing-dependent staleness from the read-then-subscribe gap.
Weigh what belongs outside the tree at all. Every store is a channel the team must keep disciplined, so argue for instance-local or subtree-provided state until shared reads across unrelated branches justify the cost.
## What "outside the tree" means A component's own state belongs to one instance. It is created when that instance is set up, destroyed when the instance is torn down, and visible only to that instance plus whatever it hands down as inputs. A **store** breaks both couplings: the value lives in an ordinary object created independently of the tree, so its lifetime is that of the module, the request or the test that created it, and any number of components in unrelated branches can read it without a common ancestor threading it down. That is the appeal, and it comes with one bill. Nothing about an ordinary object tells a component framework that the object changed, so the store has to tell the tree — and the tree has to be listening. ## The three-part surface | Operation | What it does | What a caller may assume | |---|---|---| | **read** (snapshot) | returns the current value | synchronous and cheap, so it is safe during a render | | **write** | changes the value, then notifies | may be restricted to named actions rather than open assignment | | **subscribe** | registers a listener, returns a release function | the listener runs *after* a write, and usually carries no payload | Two details in that table do most of the work. First, subscribing returns a **release function** — a subscription is a resource, not a fire-and-forget registration. Second, the notification usually carries nothing: it says "something changed", and the listener re-reads. That spares the store from knowing what each listener cares about, and it is why the *reading* side owns the decision about whether the change matters. ## The component's side of the contract 1. **Read while rendering**, to produce this pass's output. 2. **Subscribe once, when the instance is set up** — not on every render pass. 3. **Re-read immediately after subscribing**, and update if the value moved. 4. **Release on teardown**, and release-and-resubscribe if the component is pointed at a different store instance. Step 3 is the one people leave out. Between the read that produced the rendered output and the moment the listener is registered there is a gap; a write landing in that gap notifies nobody, because nobody was listening yet. The component then displays a value that is already wrong, and stays wrong until some unrelated render. Re-reading after subscribing closes the gap and costs one comparison when nothing happened. ## What each skipped step looks like in production - **Read without subscribe** — correct first frame, stale forever after, and it *looks* fine in a demo because something else on the page usually re-renders. - **Subscribe without the re-read** — rare, timing-dependent staleness that reproduces only under load or on a slow network. - **Subscribe inside render** — a new listener per pass, so one write schedules several updates, which cause more renders, which add more listeners. - **No release** — the store's listener list holds the closure and everything it captured, so a destroyed instance is both still called and still reachable. - **Write during render** — the component changes the value it is in the middle of rendering from; writes belong in event handlers, or in effects that run after the pass. ## Native stores and foreign stores Frameworks differ here, and the difference decides how much of the contract you write by hand. A runtime that tracks reads as they happen can treat a store built on its own reactive primitives as self-subscribing: reading it inside a component registers the dependency, and the release rides along with the instance. A runtime that re-runs the component function has no read-time tracking, so the subscription is explicit. A compile-time runtime instruments the read site during the build and generates equivalent wiring. In all of them a **foreign** store — one with its own notification channel the framework knows nothing about — needs an adapter pairing a subscribe with a synchronous read. ## When a store is the wrong home - Only one instance reads and writes the value: keep it local to that instance. - One subtree needs it and the tree shape is stable: provide it to that subtree rather than making it reachable from everywhere. - The data is owned by a server: what you want is a cache keyed by request with a staleness policy, not a plain store that happens to hold a response. A store is the right answer for client-owned state read by components with no useful common ancestor. Reach for it then, and honour the four steps.
- Why should a component re-read the store immediately after subscribing?Between the read that produced the rendered value and the moment the listener is registered, a write can land; that notification reaches nobody because nobody was listening yet. Re-reading after subscribing closes the window: if the value moved the component updates once, and if it did not, the second read costs a comparison.
- What does a subscription's release function have to do besides stopping callbacks?It must remove the listener from the store's list, not merely set an inert flag. A listener kept in that list holds the closure and everything the closure captured, so the instance stays reachable, and every later write still pays to call a callback that does nothing.
- Is a store necessarily one globally reachable object?No. A store is any state with a read, a write and a subscribe that no component instance owns. It can hold a single value, be created per request or per test, and be handed in explicitly. A module-level instance is only the most common packaging, and the one that causes the most trouble on a server.
A store is a noticeboard in a shared hallway: anyone walking past can read it, but only people who put their name on the notify list get told when it changes — and someone moving out has to take their name off.
saying these in an interview costs you the question
- Thinks one read at setup keeps a component current forever.
- Never releases the subscription, expecting teardown to find it.
- Subscribes inside render, creating a new listener every pass.
- Assumes any store write updates every component automatically.
- Calls a store write during render rather than from an event.