Browse documentation

QUICK START

Build a counter

Two files and a few commands. Click the counter, then make it your own.

Counter

0

Create the project

Create a Vite TypeScript project, install Typed, and start the development server:

npm create vite@latest typed-counter -- --template vanilla-ts
cd typed-counter

npm install --save-exact effect@4.0.0-rc.115 @typed/fx@2.0.0-beta.11 @typed/template@1.0.0-beta.11 @typed/ui@1.0.0-beta.11

npm run dev

Keep the starter’s package.json and index.html. Add the two files below.

Add the counter

Create src/Counter.ts. The count is a RefSubject: the template observes its value, and the button Effects update it.

import { RefSubject } from "@typed/fx";
import { component, html } from "@typed/template";

export const Counter = component(function* () {
  // Allocate per mounted view so separate counters keep independent state.
  const count = yield* RefSubject.make(0);

  return html`<section class="counter-demo" aria-label="Counter">
    <h2>Counter</h2>
    <div class="counter-demo__controls">
      <button type="button" onclick=${RefSubject.decrement(count)}>−</button>
      <output aria-live="polite">${count}</output>
      <button type="button" onclick=${RefSubject.increment(count)}>+</button>
    </div>
  </section>`;
});

component runs the generator for each mounted counter, so each gets its own count. An onclick binding runs its Effect when that button is clicked.

Replace src/main.ts to render the counter into the page:

import { Counter } from "./Counter.js";
import { Fx } from "@typed/fx";
import { DomRenderTemplate, html, render } from "@typed/template";
import { Effect, Layer } from "effect";

// Keep the running lifetime here; importing Counter should not mount it.
await render(html`<main>${Counter}</main>`, document.body).pipe(
  Fx.drainLayer,
  Layer.provide(DomRenderTemplate),
  Layer.launch,
  Effect.runPromise,
);

Open the URL printed by Vite. Click +: the count changes from 0 to 1. You have a running Typed application.

Make one value follow another

Next, derive a value from the count. Then build the Todo app to practice forms, keyed lists and persistence.

Optional: inspect mounting or add server rendering

To inspect mounting, start with a plain template.

Render the counter on the server, then restore its state in the browser. This is a separate path; TodoMVC stays client-side.