packages/component/docs/components.md
All components follow a consistent two-phase structure.
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></div>
}
}
When a component is rendered:
First Render:
handle and the setup prophandle.queueTask() are executed after renderingSubsequent Updates:
setup prop is stripped from propsComponent Removal:
handle.signal is abortedaddEventListeners() are automatically cleaned upThe 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
mix={[
on('click', () => {
count++
handle.update()
}),
]}
>
Increment
</button>
</div>
)
}