States
What a state is, how it's read and written, callbacks and their lifetime, and how changes reach readers.
States
A state is a value that tells everyone reading it when it changes, and everything reactive in the library is built on this one idea.
Here's the smallest example, a number and a button that adds to it. The button only changes count, and the label keeps
up on its own:

class Counter : ReactiveComponent {
protected override GameObject Construct() {
var count = Remember(0);
return new Layout {
FlexController = {
FlexDirection = FlexDirection.Column,
AlignItems = Align.Center,
JustifyContent = Justify.Center,
Gap = 2.pt
},
Children = {
new Label {
FontSize = 9f,
sText = count.Map(x => x.ToString())
},
new BsTextButton {
Text = "+1",
OnClick = () => count.Value++
}
}
}.Use();
}
}Remember creates a state with a starting value, here 0. Reading it always gives you the latest value, even if the
readers haven't heard about the change yet. Writing to it lets the readers know, unless the new value equals the
current one, in which case nothing happens. "Equal" is whatever EqualityComparer<T>.Default says, which by default
means the same object for a class and the same contents for a record.
Views read a state through a binding, an s-prefixed property like sText that stays in sync with it.
Property Bindings covers those in detail. To run your own code when a state changes,
use a callback, as described below.
If you change an object without assigning a new one, the state can't tell and readers won't hear about it. Call
EnforceInvalidation() to let them know anyway.
Callbacks
A callback runs code when a state changes, and there are two ways to add one:
count.AddCallback(x => Debug.Log(x));
count.AddCallback(x => Debug.Log(x), lazy: true);The first runs once right away with the current value, then again on every change. That's usually what you want, since
whatever the callback does to apply a value gets done for the starting value too. lazy: true skips that first call, so
use it for callbacks that are about changes rather than values, like a log, a sound or an animation trigger.
Below you can see one of each added to the same state with their runs counted, so the eager count starts at 1 and the lazy one at 0:

class Callbacks : ReactiveComponent {
protected override GameObject Construct() {
var value = Remember(0);
var eagerRuns = Remember(0);
var lazyRuns = Remember(0);
value.AddCallback(_ => eagerRuns.Value++);
value.AddCallback(_ => lazyRuns.Value++, lazy: true);
return new Layout {
FlexController = {
FlexDirection = FlexDirection.Column,
AlignItems = Align.Center,
JustifyContent = Justify.Center,
Gap = 2.pt
},
Children = {
new Label { sText = eagerRuns.Map(x => $"eager runs: {x}") },
new Label { sText = lazyRuns.Map(x => $"lazy runs: {x}") },
new BsTextButton {
Text = "Change the value",
OnClick = () => value.Value++
}
}
}.Use();
}
}AddCallback returns a StateSubscription you can call RemoveCallback() on to stop listening, and until you do, the
plain form above keeps running. That's fine when the state and the subscriber go away together, as they do for a state
a component created for its own tree, but becomes a leak when the state outlives the subscriber. A settings state in a
service, read by a screen that opens and closes many times, keeps every closed screen's callbacks alive, and the screens
along with them.
For a shared state, use the overloads that take the subscriber:
settings.Volume.AddCallback(this, (self, x) => self._slider.Value = x);
settings.Theme.AddCallbackUnity(image, (img, x) => img.color = x);
settings.Width.AddCallbackWeak(modifier, (mod, x) => mod.Size = x);All three remove the subscription for you, the next time the state notifies after the target is gone. They differ in how they tell that it's gone:
AddCallback(target, ...)takes anyILifetimeProvider, which every component is, and checksIsAlive.AddCallbackUnitytakes a Unity object and checks that it hasn't been destroyed.AddCallbackWeakholds the target through aWeakReferenceand checks that it hasn't been garbage collected, for objects with no lifetime of their own, like a layout modifier.
In all three the lambda receives the target as a parameter instead of capturing it. A captured this would keep the component alive through the closure, and the liveness check
could never fail. Generated bindings use these overloads and pick the right one for the property's owner, so a bound
label never leaks.
How changes are delivered
Setting a state does two things at two different times, and most questions about states come down to this.
First, right away, the state marks everything derived from it as stale, meaning anything built with Map, Where or
RememberDerived. No functions run at this point, because a derived state simply recomputes the next time it's read.
Second, later in the frame, the state delivers the change. Every changed state with callbacks waits in a queue, which
runs once per frame, after LateUpdate and right before the UI is drawn. Each callback then runs once with the state's
latest value. Bindings are callbacks, so this is also when properties are set and the screen changes.
This is what makes states cheap. Ten writes to a state in one frame become one delivery with the final value, so a state
an animation writes every frame costs one callback per frame. A loop that updates a state a thousand times costs the
same as a single assignment. A derived state whose inputs both changed is computed once with both new values, unless
your code reads it between the two writes. Callbacks run after both writes have landed, so they never see one input
updated and the other not. A change that reaches a state along two paths is also delivered only once. A state nobody
listens to gets no deliveries at all, so reading it only through Value has no per-frame cost.
Try this demo, which writes a state 1000 times per click while its delivery count only goes up by one each time:

class Batching : ReactiveComponent {
protected override GameObject Construct() {
var count = Remember(0);
var deliveries = Remember(0);
count.AddCallback(_ => deliveries.Value++, lazy: true);
return new Layout {
FlexController = {
FlexDirection = FlexDirection.Column,
AlignItems = Align.Center,
JustifyContent = Justify.Center,
Gap = 2.pt
},
Children = {
new Label { sText = count.Map(x => x.ToString()) },
new Label { sText = deliveries.Map(x => $"{x} deliveries") },
new BsTextButton {
Text = "Write 1000 times",
OnClick = () => {
for (var i = 0; i < 1000; i++) count.Value++;
}
}
}
}.Use();
}
}Notice that the label jumps straight from 0 to 1000. The 999 values in between are never delivered, so no callback or binding sees those intermediate steps and they never reach the screen.
Callbacks can write states too, and those changes are delivered in the same pass. A cascade settles within the frame, so the screen never shows an intermediate step.
Components
Building a component with Construct and Use, composing components, the lifecycle and the host behind it, coroutines and modules.
Property Bindings
The generated s-properties that keep a property in sync with a state, how they're generated, and bindings on nested objects and your own components.