Reactivity and templates

Purity updates the DOM from the values a template reads. Start with state() for values that change, compute() for values derived from them, and html for the markup that displays them.

import { compute, html, mount, state } from '@purityjs/core';

const count = state(0);
const doubled = compute(() => count() * 2);

mount(
  () => html`
    <button @click=${() => count((value) => value + 1)}>Add one</button>
    <p>Count: ${() => count()}</p>
    <p>Double: ${() => doubled()}</p>
  `,
  document.getElementById('app')!,
);

count() reads the current value. count(5) writes a value, while count((current) => current + 1) updates from the previous value. doubled() recalculates when count changes. Pass a function to a template slot when the slot should update as its reactive values change.

Bind values and events

Purity templates use a small set of prefixes:

import { html, state } from '@purityjs/core';

const name = state('');
const canSubmit = state(false);

html`
  <label for="name">Name</label>
  <input id="name" ::value=${name} />
  <button @click=${() => console.log(name())} ?disabled=${() => !canSubmit()}>Save</button>
`;

@click attaches an event listener, ::value keeps the input and state in sync, and ?disabled sets a boolean attribute. For a one-way component property use :property; for a DOM property use .property.

Render a keyed list

Use each() when items can be inserted, removed, or reordered. Give each item a stable key so Purity can preserve its DOM node when its position changes.

import { each, html, state } from '@purityjs/core';

const todos = state([{ id: 1, text: 'Read the guide' }]);

html`
  <ul>
    ${each(
      todos,
      (todo) => html`<li>${() => todo().text}</li>`,
      (todo) => todo.id,
    )}
  </ul>
`;

The render callback receives an accessor for the current item. Call todo() inside a reactive slot so a changed item with the same key updates its text. For long scrollable lists, each() also supports { virtual: true }; Purity manages the visible range and spacers.

Run an effect only when needed

watch() is for work outside the template, such as observing a value or coordinating with a browser API. It returns a function that stops the watcher.

import { state, watch } from '@purityjs/core';

const count = state(0);
const stop = watch(() => console.log(count()));

stop();

For component cleanup, register the stop function with onDispose(). To see how these values are typed, continue with the TypeScript guide. To inspect the reactive graph while developing, see Debugging.