astwerk

A counter

The smallest useful thing: a signal, a derived value, three handlers. The markup is static; only the numbers change. This is the WebAssembly path — the same counter generated as plain JavaScript is Counter, no WASM.

0

Double: 0

How it works

State

go
count := reactive.NewSignal(0)
doubled := reactive.Computed(func() int { return count.Get() * 2 })

NewSignal creates the state. Computed derives doubled from it — calling count.Get() inside the function is what registers the dependency, so it recomputes whenever count changes.

Hydrating the markup

go
reactive.Hydrate("#counter", func(root wasmwrap.Element) {
	reactive.BindText(root.Find("[data-count]"), func() string {
		return strconv.Itoa(count.Get())
	})
	reactive.BindText(root.Find("[data-doubled]"), func() string {
		return strconv.Itoa(doubled.Get())
	})
})

Hydrate attaches behaviour to markup the page already rendered — the demo div above, written by hand. BindText re-runs its function whenever something it reads changes, and writes the result into the element.

The buttons

go
reactive.On(root.Find("[data-inc]"), "click", func(wasmwrap.Event) {
	count.Update(func(v int) int { return v + 1 })
})
reactive.On(root.Find("[data-dec]"), "click", func(wasmwrap.Event) {
	count.Update(func(v int) int { return v - 1 })
})
reactive.On(root.Find("[data-reset]"), "click", func(wasmwrap.Event) {
	count.Set(0)
})

On is el.On plus automatic cleanup inside an effect. Update reads and writes atomically: increment, decrement and reset each change the signal, and both bindings re-run.

Full source
go
count := reactive.NewSignal(0)
doubled := reactive.Computed(func() int { return count.Get() * 2 })

reactive.Hydrate("#counter", func(root wasmwrap.Element) {
	reactive.BindText(root.Find("[data-count]"), func() string {
		return strconv.Itoa(count.Get())
	})
	reactive.BindText(root.Find("[data-doubled]"), func() string {
		return strconv.Itoa(doubled.Get())
	})

	reactive.On(root.Find("[data-inc]"), "click", func(wasmwrap.Event) {
		count.Update(func(v int) int { return v + 1 })
	})
	reactive.On(root.Find("[data-dec]"), "click", func(wasmwrap.Event) {
		count.Update(func(v int) int { return v - 1 })
	})
	reactive.On(root.Find("[data-reset]"), "click", func(wasmwrap.Event) {
		count.Set(0)
	})
})
How this demo runs

scripts/demo.go is compiled to demo.wasm by CompileFrom and loaded by WasmScript — see Scripts & WebAssembly. Without WebAssembly the page still renders; only the numbers stay put.