Weft
v0.31.0GitHub

Reactivity

Previously you mounted a static counter shell. Now wire it up. This is the defining move in Weft: weave a stream through the tree, and only that point updates.

Wire up the counter

Use Effect's SubscriptionRef for component-local state. SubscriptionRef.changes(ref) returns a Stream that emits the current value and then every update. Pass that stream (or a derived stream) as a child and the DOM at that spot becomes live. Replace src/app.ts:

typescript
// src/app.ts
import { h } from "@weftui/core";
import { Effect, Stream, SubscriptionRef } from "effect";

export const App = () =>
  Effect.gen(function* () {
    const count = yield* SubscriptionRef.make(0);
    const label = Stream.map(SubscriptionRef.changes(count), (n) => `Count: ${n}`);

    return yield* h.div({ class: "app" }, [
      h.h1("Weft Counter"),
      h.p({ class: "count" }, [label]),
      h.div({ class: "controls" }, [
        h.button(
          { type: "button", onclick: () => SubscriptionRef.update(count, (n) => n - 1) },
          "−",
        ),
        h.button(
          { type: "button", onclick: () => SubscriptionRef.update(count, (n) => n + 1) },
          "+",
        ),
      ]),
    ]);
  });

main.ts and index.html don't change. Reload and the buttons work.

Why this works

App's body runs exactly once: it creates the ref, builds the tree, and returns. Nothing re-invokes it afterward. The only thing that changes the DOM is the label stream woven into h.p.

Click + and SubscriptionRef.update pushes a new value, label emits "Count: 1", and the renderer patches just that paragraph's text in place. No diff, no re-render, no sibling touched.

label also shows deriving values: because SubscriptionRef.changes(count) is a Stream, you shape reactive text with ordinary stream operators (Stream.map here) instead of a templating syntax. Anywhere you'd compute a derived value, map the stream.

Note. A stream-shaped child or prop is reactive; a static value ("Hello", 5) is not and never changes. h.h1("Weft Counter") above is static for exactly that reason. The rule is uniform across the whole tree.

The full model is The Rendering Model; the vocabulary of stream-shaped values is Reactive Primitives.

Next