Context

To pass a value down through many layers by hand is tedious. Every call in between must carry it. Context lets an outer scope provide a value under a name. A scope nested inside it reads that value directly, without threading it through everything in between.

Reactive.Context('theme', theme);   // provide it once, up high
// ...deep inside some nested block...
const theme = Reactive.Use('theme');  // read it, no threading in between
Provide a value by name, read it anywhere nested inside

Providing a Value

We store the provision on the scope. A scope gains one more field beside its nodes and its owner. It is a contexts object, a map of name to value, made on first use:

const scope = {
  nodes: [],
  parentNode,
  currentEmissionsRef: undefined,
  contexts: undefined,   // name → value, made when the first Context is provided
};
A scope can now hold context, alongside its nodes and owner

Context writes into the current scope's map, so everything built after it in this scope, and in every scope nested inside it, can read the name:

Reactive.Context = (name, value) => {
  const scope = Reactive._currentScope;
  if (scope.contexts === undefined) {
    scope.contexts = {};
  }
  scope.contexts[name] = value;
};
Provide a value under a name in the current scope

Reading It

Use goes up the scope tree. It checks the current scope first, then the scope its owner sits in, and so on to the root. It returns the first contexts that has the name. A nearer provision shadows a further one. A name that no scope provides is a mistake. We catch it at build:

Reactive.Use = (name) => {
  let scope = Reactive._currentScope;
  while (scope !== undefined) {
    if (scope.contexts !== undefined && Object.prototype.hasOwnProperty.call(scope.contexts, name)) {
      return scope.contexts[name];
    }
    const owner = scope.parentNode;   // the node that owns this scope
    scope = owner === undefined ? undefined : owner.scope;   // climb to the owner's scope
  }
  throw new Error('Reactive.Use: no context named "' + name + '"');
};
Read the nearest provision, climbing the scope tree

Because it goes up the live scope tree, a block raised later, an If body or an Each entry, still finds a context that an ancestor provided. Its owner leads back up to the scope that set it.

It Is Just a Value

Use returns exactly what you provided, usually a reactive value. So a calculation over it tracks it like any other input. When you set the provided state, Reactive updates every reader, wherever they sit in the tree:

const label = Reactive.Calculate([Reactive.Use('theme')], (t) => 'theme: ' + t);
// label reflows whenever the provided theme state changes
A context value is a value; readers track it

Running It

The root provides a reactive theme and a plain level. Inside an If body, Use('theme') finds the ancestor's theme. Use('level') sees the outer 1, until a nearer Context shadows it with 2:

const root = Reactive.Root(() => {
  const theme = Reactive.State('dark');
  Reactive.Context('theme', theme);
  Reactive.Context('level', 1);
  Reactive.If(true, () => {
    Reactive.Context('level', 2);              // shadows the outer level
    Reactive.Emit('themed', Reactive.Use('theme'));  // 'dark', from the ancestor
    Reactive.Use('level');                          // 2, the nearer provision
  });
  Reactive.Use('level');                            // 1, the outer provision
});
// later: setting the theme state updates everything that used it
Provided down the tree, read where needed, nearer shadows further

Next Steps

Context threads a value down the scope tree, so a deep block reads what an ancestor provided without every layer carrying it. Next, If is the first change of shape. It is a block that builds in its own scope only when a condition holds.