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 sets
the node's own global-pending flag instead:
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, false, false);
const bodyScope = conditionIsPresent ? Reactive._scopeCreate(ifNode, callback) : undefined;
payload.if_.optionalScope = bodyScope;
ifNode.value = Reactive._emitIfValue(pending, bodyScope);
ifNode.pending = Reactive._emitIfPending(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, born with an empty _emitEmpty value and false pending; then it
becomes the body scope's parentNode. Once the body is known we fill in both the node's value and its
global pending. 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.
The value and the global pending are two separate fills. _emitIfValue returns the body's
emissions, or the empty _emitEmpty when the condition is
pending or no body stands. _emitIfPending is the global: true when the condition is
pending, since its body is unknown and anything might come, or when the standing body is itself globally pending:
Reactive._emitIfValue = (conditionPending, bodyScope) => {
if (conditionPending || bodyScope === undefined) { return Reactive._emitEmpty; }
return Reactive._emitScopeValue(bodyScope);
};
Reactive._emitIfPending = (conditionPending, bodyScope) => {
if (conditionPending) { return true; }
return bodyScope !== undefined && Reactive._emitScopePending(bodyScope);
};
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.