If
So far a program's shape is fixed: the same nodes every time. If is the first piece that changes the
shape. It builds a block of the program only when a condition holds. That block disappears when the condition
stops. That block is a scope of its own, nested inside the one around it.
const open = Reactive.State(true);
Reactive.If(open, () => {
Reactive.Constant('hi');
});
Nested Scopes
The root scope is opened by Reactive.Root. An
If's body is a scope of its own, so it calls the same _scopeCreate to open one on top of
the current scope, owned by the If node. _scopeCreate restores the previous
scope when its callback returns. As a result, the body's nodes land in the new scope, and everything after the If
goes back to the surrounding one.
Reactive.If
Reactive.If reads the condition. When it is present, Reactive.If builds the body in its own scope, owned by the If
node. The node keeps that optional scope, the builder, and a contentsUpdated flag, so a later
update can build or remove the block when the condition flips. The condition
is the If's one input, so the graph knows the If depends on it. Its value is its body's
emissions; a pending condition defers the body and
contributes only the global-pending flag:
Reactive.If = (condition, callback) => {
const currentScope = Reactive._currentScope;
const conditionRef = Reactive._resolveInput(condition);
const conditionIsPresent = Reactive._value(conditionRef);
const inputNodeRefs = [conditionRef];
const pending = Reactive._anyPending(inputNodeRefs);
const payload = { if_: { optionalScope: undefined, callback, contentsUpdated: false } };
const ifNode = Reactive._nodeCreate(payload, Reactive._emitEmpty, inputNodeRefs, pending, false);
const bodyScope = conditionIsPresent ? Reactive._scopeCreate(ifNode, callback) : undefined;
payload.if_.optionalScope = bodyScope;
ifNode.value = Reactive._emitIfValue(pending, bodyScope);
currentScope.currentEmissionsRef = Reactive._emitAppend(currentScope.currentEmissionsRef, ifNode);
};
We create the If node before we build the body, because the body scope stores the node that owns it. So
the node has to exist first; then it becomes the body scope's parentNode, and we fill in the value once
the body is known. The If manages a section, not a data value of its own: its value is whatever its
body emits, and the real work is adding or removing that section as the condition flips, which the update walk does.
Running It
When the condition holds, the body is built in a child scope, owned by the If node at the top level:
const root = Reactive.Root(() => {
const open = Reactive.State(true);
Reactive.If(open, () => {
Reactive.Constant('hi'); // inside the body scope
});
});
const ifNode = root.rootScope.nodes.find((n) => n.payload.if_);
const bodyScope = ifNode.payload.if_.optionalScope;
bodyScope.parentNode === ifNode; // true the If node owns the body scope
bodyScope.destroyed; // false the body is live
bodyScope.nodes.length; // 1 the 'hi' constant lives in the body
When the condition is false, the body is skipped: the If node holds no scope, and no body scope exists.
const root = Reactive.Root(() => {
const open = Reactive.State(false);
Reactive.If(open, () => {
Reactive.Constant('hi');
});
});
const ifNode = root.rootScope.nodes.find((n) => n.payload.if_);
ifNode.payload.if_.optionalScope; // undefined the body was not built
Next Steps
The program can now change shape. An If builds a block in its own scope, and keeps the scope and
the builder it needs to add or remove that block later. Next, Updates makes the graph react. A
change to a state flows forward through the scopes, and a section appears or disappears as its condition flips.