Core API

The whole framework is nine functions.

import {
  html, mount, createState, computed, each, effect, boundary, configure, onCleanup
} from "@zoijs/core";
FunctionPurpose
htmlTagged-template renderer (real HTML, parsed once)
mount(component, target)Render a component; returns unmount()
createState(value)A reactive value — get / set / peek
computed(fn)A lazy, cached, value-gated derived value
effect(fn)Run a side effect when reads change; returns { dispose }
boundary(child, fallback)Render a fallback if a subtree throws while building
each(items, keyFn, renderFn)Keyed list rendering
configure({ dev })Toggle development warnings
onCleanup(fn)Teardown for a component or list item

html#

A tagged template that turns real HTML into live DOM. Interpolate text, attributes, and event handlers. Wrap a value in an arrow to make it reactive.

html`<button onclick=${onClick} class=${() => cls}>${() => label}</button>`
Text is inserted as inert text — script in a string never executes. See the FAQ on security.

mount#

Runs a component once, inserts its DOM into a target (element or selector), and returns an unmount() that disposes everything it created.

const unmount = mount(Counter, "#app");
// later…
unmount();

createState#

A reactive value. Reading it inside a binding subscribes that binding; writing it updates only the dependents that actually changed.

const count = createState(0);
count.get();        // 0  (subscribes inside a binding)
count.set(5);       // updates dependents
count.peek();       // read without subscribing

computed#

A derived value that is lazy and cached. It recomputes only when a dependency changes, and won't wake the DOM if its result is unchanged.

const first = createState("Ada");
const last = createState("Lovelace");
const full = computed(() => first.get() + " " + last.get());

full.get(); // "Ada Lovelace" — cached, recomputed only when a source changes

effect#

Run a side effect that re-runs when a reactive value it reads changes — for work outside the view (persist, set the title, drive a non-Zoijs widget). For on-screen content, use a binding. It may return a cleanup (runs before each re-run and on dispose), returns { dispose }, and auto-disposes with its owner.

const theme = createState("light");

// runs now, and re-runs whenever `theme` changes
effect(() => localStorage.setItem("theme", theme.get()));

// return a cleanup — runs before each re-run and on dispose
effect(() => {
  const id = setInterval(tick, 1000);
  return () => clearInterval(id);
});

boundary#

Render a subtree; if it throws while building its markup (an error that would otherwise break the whole mount), tear down the partial work and render a fallback instead. For untrusted subtrees — third-party widgets, foreign data. Catches synchronous setup/render throws only.

html`<section>
  ${boundary(
    () => RiskyWidget(),
    (err) => html`<p>Couldn't load this.</p>`
  )}
</section>`

each#

Keyed list rendering. Reordering moves DOM nodes instead of rebuilding them, preserving focus, input values, and scroll position.

html`
  <ul>
    ${each(
      () => todos.get(),     // items (reactive)
      (t) => t.id,           // stable key
      (t) => html`<li>${() => t.text}</li>`
    )}
  </ul>
`

configure#

Toggle development warnings. Leave them on in development; turn them off in production for a little less overhead.

configure({ dev: false });

onCleanup#

Register a teardown function for the current component or list item. It runs when that component unmounts or that item is removed — perfect for timers and subscriptions.

function Clock() {
  const now = createState(Date.now());
  const id = setInterval(() => now.set(Date.now()), 1000);
  onCleanup(() => clearInterval(id));   // runs on unmount
  return html`<time>${() => now.get()}</time>`;
}