ajo

Interface

Interfaces with Ajo

Plain functions, generator components and explicit local state.

A view is a function

Stateless components receive arguments and return JSX. Use class for CSS classes, string values for inline styles, and set: for DOM properties and event handlers.

Greeting.tsx
export const Greeting = ({ name }: { name: string }) => (
  <p class="greeting">Hello, {name}.</p>
)

State lives before the loop

A generator body starts once. Values declared before the rendering loop persist. this.next() advances the generator and updates the view.

Counter.tsx
import type { Stateful } from 'ajo'

const Counter: Stateful = function* () {
  let count = 0

  while (true) yield (
    <button set:onclick={() => this.next(() => count++)}>
      Count: {count}
    </button>
  )
}

Read fresh arguments

Use for…of this when each render needs current arguments. Give list items stable keys so their component identity follows the data.

Stepper.tsx
import type { Stateful } from 'ajo'

export const Stepper: Stateful<{ step: number }> = function* () {
  let count = 0
  for (const { step } of this) yield (
    <button set:onclick={() => this.next(() => count += step)}>
      {count} · add {step}
    </button>
  )
}

Keep cleanup with the component

Attach listeners and requests to this.signal, which aborts when the component ends. Guard browser-only work during server rendering. A reusable clove shares this same host lifecycle.

Inside a stateful component
if (typeof document !== 'undefined' && this.nodeType === 1) {
  document.addEventListener('keydown', onKey, {
    signal: this.signal,
  })
}