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');
});
A block that is built only while the condition holds

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);
};
Create the If node first, build the body in its own scope, then thread its emissions on

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
The block is a scope of its own, hanging off the If node

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
A false condition builds nothing

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.