All components follow a consistent two-phase structure.
- Setup Phase - Runs once when the component is first created
- Render Phase - Runs on initial render and every update afterward
function MyComponent(handle: Handle, setup: SetupType) {
// Setup phase: runs once
let state = initializeState(setup)
// Return render function: runs on every update
return (props: Props) => {
return <div>{/* render content */}</div>
}
}When a component is rendered:
-
First Render:
- The component function is called with
handleand thesetupprop - The returned render function is stored
- The render function is called with regular props
- Any tasks queued via
handle.queueTask()are executed after rendering
- The component function is called with
-
Subsequent Updates:
- Only the render function is called
- Setup phase is skipped, setup closure persists for the lifetime of the component instance
- Props are passed to the render function
- The
setupprop is stripped from props - Tasks queued during the update are executed after rendering
-
Component Removal:
handle.signalis aborted- All event listeners registered via
handle.on()are automatically cleaned up - Any queued tasks are executed with an aborted signal
The setup prop is special—it's only available in the setup phase and is automatically excluded from props. This prevents accidental stale captures:
function Counter(handle: Handle, setup: number) {
// setup prop (e.g., initialCount) only available here
let count = setup
return (props: { label: string }) => {
// props only receives { label } - setup is excluded
return (
<div>
{props.label}: {count}
</div>
)
}
}
// Usage
let element = <Counter setup={10} label="Count" />The simplest component just returns JSX:
function Greeting() {
return (props: { name: string }) => <div>Hello, {props.name}!</div>
}
let el = <Greeting name="World" />Props flow from parent to child through JSX attributes:
function Parent() {
return () => <Child message="Hello from parent" count={42} />
}
function Child() {
return (props: { message: string; count: number }) => (
<div>
<p>{props.message}</p>
<p>Count: {props.count}</p>
</div>
)
}State is managed with plain JavaScript variables. Call handle.update() to trigger a re-render:
function Counter(handle: Handle) {
let count = 0
return () => (
<div>
<span>Count: {count}</span>
<button
on={{
click() {
count++
handle.update()
},
}}
>
Increment
</button>
</div>
)
}- Handle API - Complete handle API reference
- Patterns - State management best practices