Skip to main content
Vertz reactivity is compiler-driven. You write plain let and const — the compiler makes them reactive. There are no hooks, no dependency arrays, and no special imports for component state.

State with let

Declare mutable state with let. The compiler transforms it into a reactive value that updates the DOM when it changes.
function Counter() {
  let count = 0;

  return (
    <button
      onClick={() => {
        count++;
      }}
    >
      Clicked {count} times
    </button>
  );
}
That’s it. When count changes, only the text node updates — no re-render of the entire component.

Derived values with const

Derive values with const expressions. The compiler tracks their dependencies and recomputes them when needed.
function TaskSummary({ tasks }: { tasks: Task[] }) {
  let statusFilter = 'all';

  const filtered = statusFilter === 'all' ? tasks : tasks.filter((t) => t.status === statusFilter);

  const summary = `${filtered.length} tasks`;

  return (
    <div>
      <select
        onChange={(e) => {
          statusFilter = e.target.value;
        }}
      >
        <option value="all">All</option>
        <option value="done">Done</option>
      </select>
      <p>{summary}</p>
    </div>
  );
}
When statusFilter changes, filtered and summary recompute, and only the affected DOM nodes update.

What the compiler does

The compiler transforms your code at build time. You never see or interact with the output — this is purely an implementation detail:
You writeCompiler handles
let count = 0Reactive state that tracks reads and notifies on write
const doubled = count * 2Lazy derived value that recomputes when dependencies change
<span>{count}</span>Fine-grained DOM binding that updates only the text node
<div className={isActive ? 'on' : 'off'}>Reactive attribute binding

Rules

  • Use let for values that change. Use const for values derived from other values.
  • Mutations in event handlers (onClick, onInput, etc.) trigger updates automatically.
  • You don’t need to import anything for component-level reactivity.

Low-level APIs

These APIs are implementation details. You should not need them in normal component code. They exist for library authors, custom stores, and non-component contexts (e.g., a shared state module outside of any component).

batch()

Group multiple state changes into a single update flush. Useful in async callbacks or store logic where multiple values change at once.
import { batch } from '@vertz/ui';

function resetForm() {
  batch(() => {
    name = '';
    email = '';
    status = 'idle';
  });
  // DOM updates once, not three times
}

Signature

function batch(fn: () => void): void;
Nested batch() calls are supported — only the outermost batch triggers the flush.

untrack()

Read a reactive value without creating a dependency. Rarely needed — only useful when you intentionally want to skip reactivity for a specific read.
import { untrack } from '@vertz/ui';

// Inside a derived expression, read `count` without subscribing to it
const label = `${title}: ${untrack(() => count)}`;

Signature

function untrack<T>(fn: () => T): T;