The advantage of Imperative.js over other frameworks is its simple, imperative code, with strong guarantees about ordering and state. However, sometimes you need to get around this strict structure. Imperative.js provides a mechanism for this with state variables, available in imperative/state.

If you are considering using a state variable, take a little while to look for another option. State variables cause “action at a distance” which is hard to reason about. If you can obtain the same effect using other features—even relatively advanced features like custom output types or returnNext—then this is preferable. Another option is to refactor your UI to remove “action at a distance” in favor of other interactions, which might be more intuitive. Structured control flow is not only helpful to the programmer, it can make an application more comprehensible to the user as well.

With that said, Imperative.js is not meant to be a purist framework, and if you are mangling your program structure to enable a little bit of shared state then you should probably just use a state variable.

If State = require("imperative/state"), then the state primitives are state = State.make(value), value = State.get(state), State.set(state, value) and value = yield* State.watch(state). The state variable itself is just a tag, which looks like {gensym: 1234}. The value must be accessed through set, get, and watch. If you mutate a state variable, such as pushing to an array, then it will not activate any watch widgets; it is usually better to treat state variables as immutable.

watch is a widget, which yields a special nil DOM which disappears in the output. It returns whenever the state value changes. The example below shows how two widgets can communicate with a state variable, without returning control to the parent function.

const State = require("imperative/state")
const Widget = require("imperative/widgets")

function* nameInput(nameState) {
  let inputState = {}
  while(true) {
    inputState = (yield* Widget.textInput({}, inputState)).state
    State.set(nameState, inputState.value)
  }
}

function* nameOutput(nameState) {
  let name = State.get(nameState)
  while(true) {
    name = yield* I.h('span', {}, [I.h('span', {}, "Your name is: " + name), State.watch(nameState)])
  }
}

example("State", function*() {
  let nameState = State.make("No Name")
  yield* I.h('div', {}, [nameInput(nameState), nameOutput(nameState)])
}())