docs / Language

Reactivity

Tracked cells, the cell API, microtask-batched scheduling, effects, and the flushSync/tick escapes. No virtual DOM.

Vesk reactivity is built on tracked cells created with `track()`. Reading a cell inside a component body or `effect()` subscribes; writing a cell schedules an update. There is no virtual DOM: the compiler emits per-cell DOM update code.

Cell API

Function Purpose
track(initial) Create a reactive cell
get(cell) Read a value (subscribes inside effects)
set(cell, v) Write a value; Object.is-guarded, schedules subscribers
increment(cell) / decrement(cell) Post-increment/decrement a numeric cell; returns the original value
untrack(fn) Run fn without subscribing
peek(cell) Read a cell value without subscribing
derived(fn) Computed cell that re-runs fn when its dependencies change
effect(fn) Run fn now and on every dependency change
flushSync(fn) Run fn with the scheduler in synchronous mode
tick() Resolve after the next animation frame
on_destroy(fn) Register teardown cleanup for the current block/component

All of these are auto-imported from `@vesk/runtime` when used inside components — no import statement needed.

Scheduler semantics

  • `set()` does not update the DOM synchronously — updates are microtask-batched, and multiple writes in one turn produce one flush.
  • `effect()` runs immediately on creation, then on dependency change.
  • `flushSync(fn)` flushes pending updates, runs fn with immediate DOM writes, and restores the async mode afterwards.
  • `await tick()` resolves after the frame the flush has painted.
  • The scheduler guards against effect loops — after 1001 flush rounds it throws: "Maximum update depth exceeded. This typically indicates that an effect reads and writes the same piece of state."

`batch` does not exist in the runtime. For synchronous multi-write flushes use `flushSync(fn)` — importing `batch` from `@vesk/runtime` does not resolve.

Example

app/components/Counter.vsk
										
											component Counter {
  let &[count] = track(0);

  effect(() => {
    console.log("count is", count);
  });

  <button onClick={() => count++}>
    Count: {count}
  </button>
}