Skip to content
Mercury
English
Design Book
Menu

Reflect.7. Following a lifetime

A value can change many times during one component lifetime. Reflect hooks capture the resolved props for that lifetime; they are not subscriptions to every later prop value.

Reflect 4.1.1 · Interactive exercise

Open the exercise on its own page

Identify the lifetime being observed

hooks.mounted runs after mounting. hooks.unmounted runs when that reflected lifetime ends. Either hook can be a callback, event or effect, and the hook invocation is scoped. Both receive the resolved props captured for that lifetime.

import {createElement as h} from "@fiberfx/hyperscript";
import {event, store} from "@fiberfx/hypergraph";
import {reflect} from "@fiberfx/reflect";

const changed = event<string>();
const recorded = event<string>();
export const $name = store("Ada").on(changed, (_, name) => name);
export const $history = store<string[]>([])
  .on(recorded, (history, entry) => [...history, entry]);
export const Lifetime = reflect({
  view: (p: {name: string}) => h("p", null, p.name),
  bind: {name: $name},
  hooks: {
    mounted: props => recorded(`mounted: ${props.name}`),
    unmounted: props => recorded(`unmounted: ${props.name}`),
  },
});

The owner controls whether Lifetime is rendered, for example with visible ? h(Lifetime, {}) : null. Read $history in a separate view through the shared binding. Keep both under the intended Provider.

An ordinary prop update does not begin a new hook lifetime. A scope replacement releases the previous hook lifetime and starts another. Reordering a stable keyed row does not end it. The hooks on variant belong to its wrapper, so choosing another case does not by itself rerun those wrapper hooks.

Mount, update and unmount

Open the lifecycle experiment. Start with Ada and choose Mount view. Change the name to Grace while the view remains mounted. Then choose Unmount view.

Before unmounting, predict both the text in the view and the name that will appear in the hook history. They answer different questions: the current render versus the props captured at the start of this lifetime.

Read current and captured values

While mounted, the view should update to Grace. The hook history should contain mounted: Ada and then unmounted: Ada. A prop update should not insert an extra mount entry.

Mount again while the name is Grace. This new lifetime should capture Grace. The exercise exposes an ordered history for inspection; it does not automatically certify that every application resource was released. Record and assert the specific resource you own when testing real cleanup.

Reject the wrong cleanup assumption

The prediction “unmounted receives the latest displayed name” is wrong for these hooks. Do not release a resource under its latest label if it was acquired under a different captured key. Pair acquisition and cleanup using the same lifetime identity; place value-driven effects in the model or in a component hook designed for that dependency.

A returned cleanup function from hooks.mounted is not the Reflect API: its return value is ignored. Use the separate unmounted hook when that contract fits. Resource descriptors, automatic disposer tracking and a new lifecycle DSL are not exported in 4.1.1.

Start a clean observation

Use Restart the whole exercise, then repeat Ada → mount → Grace → unmount. Restart tears down a stage as well as resetting model values; teardown may itself produce a cleanup entry. Read the resulting history rather than assuming reset means no cleanup occurred.

For a test, unmount first, assert cleanup, then clear the recorded history or dispose the owned scope. Reversing that ownership order can hide cleanup or leave it with a disposed scope.

Check the server boundary

Server rendering reads scoped values but runs neither mounted nor unmounted hooks. Render the same view under two request scopes with different names and compare the HTML; assert that the hook recorder remains empty. This checks scoped output and hook silence separately.

Do not start browser-only work from a projection: projections also participate in server rendering. Put browser resource ownership at a client lifetime boundary, and let the server own request-scope creation and disposal. The final chapter turns the observable contracts into regression checks.

Previous · Course · Next