Learn
Thinking in Vidact
How state, derived values, and effects work when a component runs once per mount.
Vidact uses React function components, JSX, and hooks. A component builds its DOM when it mounts. After that, generated updaters respond to changes in state, props, context, and external stores.
Components run once
In React, a component is a function that runs every time its state or props change. React calls it, gets back a new tree of elements, and diffs that tree against the previous one to work out what to change in the DOM.
In Vidact, a component is a function that runs once, when it mounts. The compiler reads the function ahead of time and works out which parts of the output depend on which values. It then emits two things: code that builds the DOM, and a list of small updaters, each tied to the values it reads. When you call a state setter, Vidact runs the updaters that depend on that value and nothing else.
Take this component:
import { useState } from 'react'export function Quantity() { const [quantity, setQuantity] = useState(1) const total = quantity * 24 return ( <div> <button onClick={() => setQuantity(quantity + 1)}>Add one</button> <p>{quantity} items, ${total}</p> </div> )}React would re-run Quantity on every click, recompute total, rebuild the element tree, and diff it. Vidact compiles it to roughly this:
- Create the
<div>,<button>, and<p>elements and attach the click listener. - When
quantitychanges: recomputetotal, then update the two text nodes inside<p>.
What stays the same
The supported subset includes:
useState,useReducer,useRef,useMemo,useCallback,useIduseEffect,useLayoutEffect,useImperativeHandle,useEffectEventcreateContext,useContext,use(context),useSyncExternalStore- JSX with fragments, conditionals,
.map()over arrays,key, spreads, andref - Custom hooks defined in the same module as their caller
- Controlled and uncontrolled forms, portals, error boundaries
In the example above, the compiler tracks total as a derived value and recomputes it when quantity changes. This does not require useMemo. Memoization hooks still preserve identity where it is observable, such as effect dependencies. See the compatibility reference for the limits of each API.
What is different
A few habits from React do not carry over. Each one is a direct consequence of "the function runs once."
The component body is not a place for side effects
Anything you do directly in the body of a component happens exactly once. A console.log runs once. A fetch runs once. In React that code would run on every render, and some code accidentally depends on that. In Vidact, put work that should respond to changes into useEffect, and put reads of external mutable state into useSyncExternalStore.
Handlers always see current values
In React, the closure a handler captured may hold an older value of state, which is why functional updates like setCount(c => c + 1) exist. In Vidact the compiler rewrites state reads so that a handler reads the value at the moment it runs. setCount(count + 1) and setCount((c) => c + 1) behave the same. Functional updates are still good style when several writes happen in one event.
Props and outer variables are read-only during construction
The compiler rejects assigning to a prop, mutating a prop object, or writing to a module-level variable inside a component body, and reports a DestructiveRenderMutation diagnostic. Move the write into an event handler or an effect, or copy the value into local state.
There is no "render count" to optimize
memo does not skip component calls after mount because those calls do not occur. For performance work, measure the derived computations, effects, and DOM updates triggered by a change. A slow derived value still runs on every change to its inputs, even though the component body runs once.
Unsupported code fails at compile time
If you use a class component, a rejected Children helper, or a feature you have not enabled, the build fails with a diagnostic pointing at the expression. The troubleshooting guide explains how to read those errors.
A mental model
When you write a Vidact component, picture it as a description of a piece of DOM plus the rules for keeping it current:
- Structure. The JSX describes elements that are created once.
- Sources. State, props, context, and store snapshots are the values that can change.
- Bindings. Every expression in the JSX that reads a source becomes a binding the compiler keeps up to date.
- Effects. Anything that must happen because a source changed, outside the DOM, lives in an effect.