Components
Components are reusable UI blocks backed by their own sub-context. Each component has isolated signals, actions, and a view — but shares the parent connection. They're the building block for composable real-time UIs.
Creating a component
Define a component as a callable that configures a Context:
$counterComponent = function (Context $c) use ($twig): void {
$count = $c->signal(0, 'count');
$inc = $c->action(fn() => $count->setValue($count->int() + 1) & $c->sync(), 'inc');
$c->view('components/counter.html.twig', [
'count_id' => $count->id(),
'count_val' => $count->int(),
'inc_url' => $inc->url(),
]);
};
Mounting a component
Mount a component inside a page handler via $c->component(), giving it a namespace to prevent
signal ID collisions. The returned callable renders the component's initial HTML:
$app->page('/', function (Context $c) use ($counterComponent): void {
$counter = $c->component($counterComponent, 'counter');
$c->view('pages/home.html.twig', [
'counter' => $counter(), // initial HTML string
]);
});
{# pages/home.html.twig #}
<main>
<h1>Welcome</h1>
{{ counter | raw }}
</main>
Namespacing
The namespace prefixes all signal and action IDs in the component. This lets you mount the same component multiple times on one page without signal conflicts:
$counterA = $c->component($counterComponent, 'counter-a');
$counterB = $c->component($counterComponent, 'counter-b');
// counter-a has signal ID 'counter-a_count', action '/_action/counter-a_inc'
// counter-b has signal ID 'counter-b_count', action '/_action/counter-b_inc'
$c->view('page.html.twig', [
'counterA' => $counterA(),
'counterB' => $counterB(),
]);
Scoped components
Components can set their own scope, independent of the parent. This is how the website's shared counter and presence indicator work — they're ROUTE-scoped components inside a page:
$presenceComponent = function (Context $c) use ($app, $twig): void {
$c->scope(Scope::ROUTE); // shared with everyone on this route
$c->view(function () use ($app, $twig): string {
$count = count($app->getClients());
return $twig->render('components/presence.html.twig', [
'count' => $count,
'person' => $count === 1 ? 'person' : 'people',
]);
}, cacheUpdates: false);
};
Component lifecycle
Components follow the same lifecycle as contexts. They support onDisconnect,
setInterval, and all other context methods:
$roomComponent = function (Context $c) use ($room, $app): void {
$c->addScope('room:' . $room);
// ... signals, view ...
$c->onDisconnect(function (Context $c) use ($room, $app): void {
Room::$members[$room]--;
$app->broadcast('room:' . $room);
});
};
Next steps
- Scopes — share component state across users
- Views — how component views update independently
- Lifecycle — connect, disconnect, and timer hooks
- API: component() — full method signature