skip to content

When the ancestor that provides a value is torn down and created again, what happens to the value its descendants read?

level: seniorimportance: should knowfreq 46%

answer

  1. per instance, not per key
  2. created at setup, released at teardown
  3. no reader outlives its provider
  4. placement quietly decides lifetime
  5. siblings get two independent values

basics

~20 s

The value's lifetime is the provider instance's lifetime: a new provider instance means a freshly created value, with none of the old one's accumulated state, caches or in-flight work. Descendants are torn down with it and rebuild everything derived.

solid answer

~40 s

A provider is an ordinary component instance, so what it provides is created during its setup and released in its teardown. The value is ambient in the tree, not in time. When that ancestor is removed, its descendants go with it, whatever the value held - accumulated state, a cache, in-flight requests, subscriptions it owns - must be released by the provider, and a later mount creates a fresh value with none of the previous instance's history. This is why a provider placed inside a branch that swaps produces state that silently resets, and why a value two sibling subtrees must both keep across such swaps has to be provided by a common ancestor whose own lifetime spans them.

go deeper

for a junior

Remember that what an ancestor provides is created and destroyed with that ancestor; it is not a permanent global that sits above the tree.

for a middle

Explain the create-at-setup, release-at-teardown pairing, and why removing the provider necessarily removes every reader beneath it.

for a senior

Diagnose resets and leaks from provider placement: which branch swaps, how long the value has to live, and what the teardown fails to release.

for a principal

Set scoping policy: which values are application-lifetime, which are per-session or per-feature, who owns releasing them, and when the tree is the wrong owner entirely.

Ambient provision is often described as if the value floats above the tree. It does not. It is owned by a component instance and shares that instance's fate, and most surprises with provided values are really surprises about lifetime. ## Lifetime is the provider instance's lifetime Three moments matter: 1. **Creation.** The provided value is produced as part of the providing instance's setup - state it declares, or an object it constructs. A second provider instance produces a second value. 2. **Availability.** While that instance is mounted, every descendant read resolves to that value. Since descendants exist only under a mounted ancestor, no reader can outlive the provider. 3. **Release.** On teardown the value stops being reachable, and anything it registered with the outside world must be undone by the provider that created it. The practical statement: **the provided value is per-instance, not per-key**. The key identifies what is being provided; the instance decides which one you get and for how long. ## What teardown has to release A provided value is frequently more than data, and whatever it started, the provider must stop: - timers and repeated polling it owns; - subscriptions it opened to anything outside the subtree; - in-flight requests, cancelled through the standard abort handle so late responses cannot resolve into a dead subtree; - queued writes and retries, which should either flush before teardown or be abandoned deliberately; - cached data, which simply disappears - and is rebuilt from scratch on the next mount. Nothing else will do this for you. Descendants that merely read the value do not own its registrations, and a consumer's own teardown says nothing about the provider's. ## The symptoms this produces | symptom | usual cause | |---|---| | a provided value resets whenever a branch of the UI swaps | the provider lives inside the branch that swaps | | two parts of the screen disagree about the "same" value | each subtree renders its own provider, so there are two values | | requests complete after the subtree is gone, or handlers run on a dead value | teardown did not cancel in-flight work or drop subscriptions | | a stale value keeps being read after a remount | something captured the old instance's value instead of reading it again | | memory grows across repeated mounts | each instance's registrations were never released | ## Placing the provider deliberately 1. List every component that must read the value. 2. Find their lowest common ancestor - the shallowest node that still contains all of them. 3. Ask how long the value must live, then check that this ancestor is mounted for at least that long. If it is not, move up until you find one that is. 4. Place the provider there, and no higher. Defaulting every provision to the root makes every value application-lifetime, which loses per-scope isolation and keeps things alive that should have been released. Step 3 is the one usually skipped: placement is chosen for tree reach and then quietly determines lifetime as well. ## Two sibling subtrees that need the same value Because resolution only looks at ancestors, two siblings that each render their own provider get two independent values that never converge. The options are few and each costs something: - **Provide from their common ancestor**: one instance, one value, lifetime as long as that ancestor - at the price of a wider scope than either subtree needs, and of the value surviving swaps inside them. - **Keep two providers** and treat the values as genuinely per-subtree. Correct when each subtree really owns its own copy, wrong the moment the two are expected to agree. - **Stop using the tree as the owner.** If the value must outlive every subtree that reads it, its lifetime is not a tree lifetime at all; something outside the tree should own it and provision should merely hand down access to it. Whether the state belongs to one instance in the first place is a separate design question, and so is the choice of a home outside the tree - but the lifetime argument above is what usually forces it. ## The mental model to carry Treat a provided value as a resource on a lease held by one component instance: created with it, valid only under it, released by it, and reissued fresh on the next mount. Then "why did this reset?" and "why is this still running?" become the same question asked from either side of the lease, and the fix is almost always the provider's position in the tree rather than the read.

  • Two sibling subtrees must read the same provided value. What are the options and their costs?
    Provide it from their common ancestor, so one instance spans both and lives as long as that ancestor, at the price of a wider scope than either needs. Or keep two providers and accept two independent values, which is right only when the value is genuinely per-subtree. A value that must outlive every subtree reading it does not belong to the tree at all.
  • How do you stop a provider's teardown from losing work that is in flight?
    Treat the provided value as an owned resource with an explicit teardown: cancel in-flight requests through the standard abort handle, clear timers, drop subscriptions, and flush or deliberately abandon queued writes. Anything that must survive belongs above the provider or outside the tree, so a later mount starts clean by design rather than by accident.
  • Why does placing every provider at the root not solve lifetime problems?
    It replaces resets with the opposite failure. Everything becomes application-lifetime, so per-scope values are shared where they should have been isolated, caches never clear, and one user's or one feature's data lingers after the subtree that owned it is gone. Scope should be the smallest that covers the readers for as long as they need.

saying these in an interview costs you the question

  • Assumes a provided value survives its provider's teardown
  • Puts a provider inside a branch that swaps, then calls the reset a bug
  • Expects two sibling providers of one key to share a single value
  • Leaves timers and subscriptions owned by the provided value running
  • Defaults every provider to the root so nothing is ever scoped
  • Captures the provided value once instead of reading it again after a remount