Counter
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.112 @typed/fx@2.0.0-beta.6 @typed/template@1.0.0-beta.6 @typed/ui@1.0.0-beta.6
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 { component } from "@typed/ui/Component";
import { RefSubject } from "@typed/fx";
import { 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>`;
});
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.
Keep going
- Derive a value from the count, then learn about components and shared state.
- Render this counter on the server and restore its state in the browser.
- Build a Todo app with forms, lists, and persistence.
For a closer look at the render pipeline, start with a plain template.