Sphere and lifetime
A sphere is the unit of reactive lifetime. Every stock, memo, effect, callback, action, and resource belongs to the sphere it was created in.
If you are using the JS bridge, you never create or clear one yourself. Opening a pier creates a sphere, reading a hail creates a sphere, and unmounting the component clears them. The adapter does it.
So read this page for the model, not for an API you have to call. What matters day to day is one sentence: state lives as long as the scope it was created in, and that scope is your provider.
The rest of this page is the machinery underneath: useful if you are writing your own integration, or debugging why something outlived what you expected.
Creating one
Section titled “Creating one”let (id, result) = make_sphere(parent_id, || { let state = Stock::new(State::default()); provide_context(state); state});Everything created inside the closure is owned by the new sphere. You get back its id and whatever the closure returned.
make_top_sphere() creates an empty sphere with no parent, for a root.
This is what PierProvider calls for you.
Clearing
Section titled “Clearing”clear_sphere(id);That frees every state the sphere owns. You never free individual values, and with an adapter, you do not call this either: it runs from the framework’s own cleanup hook when your provider unmounts.
Two properties make this safe to wire into a UI framework:
- It cascades. Spheres form a parent-child tree, and clearing one clears its whole subtree.
- It is idempotent and order-independent. Clearing an already-cleared sphere does nothing.
Together these mean a host can register one clear_sphere per component and
stay correct no matter which order things unmount in. Whether a child clears
itself first or the parent’s cascade reaches it first, the second call finds
nothing to do.
Context
Section titled “Context”The parent link also drives context lookup.
provide_context(Stock::new(State::default()));
let state = use_context::<Stock<State>>().unwrap();use_context walks up the parent chain and returns None if no ancestor
provided that type. Context is keyed by type, and the nearest one wins.
Because it is keyed by type, two values of the same type collide. Wrap them:
#[derive(Clone, Copy)]struct PanelTitle(Stock<String>);
#[derive(Clone, Copy)]struct PanelSubtitle(Stock<String>);Context values must be Clone. Stocks and callbacks are Copy, so in practice
you are storing handles, not data.
Knowing where you are
Section titled “Knowing where you are”current_sphere_id() returns the sphere currently being built or run, if any.
if let Some(id) = current_sphere_id() { /* ... */ }You mostly need this when writing your own integration rather than using an adapter.
Batching across a sphere
Section titled “Batching across a sphere”batch groups writes into one propagation. batch_with_sphere does the same
while entering a specific sphere first: useful when you are writing state from
outside any runner.
batch_with_sphere(sphere_id, || { state.count().set(0); state.items().write().clear();});One last piece: how a derived stock finds its way back to the root, and the one knob you might want to turn. See the getter pipeline.