diff --git a/site/app/pages/learn/page.zx b/site/app/pages/learn/page.zx index 818c694d..57be6b9f 100644 --- a/site/app/pages/learn/page.zx +++ b/site/app/pages/learn/page.zx @@ -468,7 +468,13 @@ fn Interactivity(allocator: zx.Allocator, props: InteractivityProps) zx.Componen

Binding state to events

To update component state in response to events, use ctx.bind(handler) and access state through the event handler:

-

When you use ctx.bind(), the handler receives a zx.server.Event.Stateful which provides access to component state through event.state(T).

+

When you use ctx.bind(), the handler receives a zx.client.Event.Stateful which provides access to component state through event.state(T). Call state(T) in the same order as the ctx.state() calls in the component; the types must match.

+
+ Note: + For event handlers to run in the browser, + @rendering={"{.client}"} + must be set on the interactive component or a parent that wraps it. Without it, the component renders server-side only and event handlers are ignored. +
//

State management patterns

//

For complex applications, organize your state into structs and pass handlers for different operations:

diff --git a/site/app/pages/reference/page.zx b/site/app/pages/reference/page.zx index 43df86ac..3f1c3354 100644 --- a/site/app/pages/reference/page.zx +++ b/site/app/pages/reference/page.zx @@ -116,6 +116,43 @@ pub fn Page(ctx: zx.PageContext) !zx.Component { // const setup_elapsed_ms = @as(f64, @floatFromInt(setup_elapsed_ns)) / @as(f64, @floatFromInt(std.time.ns_per_ms)); // std.log.info("\x1b[1;36m[DOCS PAGE] Setup phase completed: {d:.3}ms\x1b[0m", .{setup_elapsed_ms}); + const csr_simple_handler_example = + \\fn handleInput(event: zx.client.Event) void { + \\ const value = event.value() orelse ""; + \\ zx.log.info("Input: {s}", .{value}); + \\} + \\// Attach: oninput={handleInput} + ; + + const csr_stateful_handler_example = + \\pub fn Counter(ctx: *zx.ComponentCtx(void)) zx.Component { + \\ const count = ctx.state(i32, 0); + \\ return ( + \\
+ \\ + \\
+ \\ ); + \\} + \\fn increment(e: *zx.client.Event.Stateful) void { + \\ const count = e.state(i32); // same order as ctx.state() + \\ count.set(count.get() + 1); + \\} + ; + + const csr_state_bind_example = + \\pub fn Counter(ctx: *zx.ComponentCtx(void)) zx.Component { + \\ const count = ctx.state(i32, 0); + \\ return ( + \\
+ \\ + \\
+ \\ ); + \\} + \\fn increment(value: i32) i32 { + \\ return value + 1; + \\} + ; + // Allocate files on heap to avoid stack lifetime issues const layout_files = blk: { const files = allocator.alloc(File, 2) catch unreachable; @@ -276,13 +313,45 @@ pub fn Page(ctx: zx.PageContext) !zx.Component {

Warning: Only use @escaping={"{.none}"} for content you trust completely. User-provided content should always use the default escaping.

@rendering

-

The @rendering attribute enables client-side rendering for interactive components.

+

The @rendering attribute enables client-side rendering for interactive components. Without it, all components render server-side only and event handlers are not active in the browser.

Values:

- + +

Client-side Zig rendering

+

Use @rendering={"{.client}"} to make a component interactive. The component is server-rendered for the initial HTML, then hydrated and re-rendered client-side on state changes. Only the marked component is managed client-side; the rest of the page stays server-rendered.

+ + +

Managing state

+

Client components that need reactive state must use zx.ComponentCtx(Props) (or zx.ComponentCtx(void) when there are no props). The context provides ctx.state(T, initial) to declare a piece of state:

+
    +
  • ctx.state(T, initial_value) - declares a reactive value of type T. Returns a State(T) handle that persists across re-renders.
  • +
  • state.get() - read the current value. Can be used directly in markup as {"{count}"}.
  • +
  • state.set(new_value) - update the value and trigger a re-render.
  • +
  • state.update(fn(T) T) - update via a transform function.
  • +
+

State is stored per-component-instance and survives re-renders. On the server (SSR), ctx.state() always returns the initial value.

+ +

Event handlers

+

ZX provides three ways to attach event handlers to client components:

+

1. Simple handler - for reading the event without touching state:

+ +

The handler receives a zx.client.Event value. Useful methods:

+
    +
  • event.value() - returns ?[]const u8 with event.target.value (for inputs and selects).
  • +
  • event.key() - returns ?[]const u8 with the pressed key name (for keyboard events).
  • +
+

2. Stateful handler via ctx.bind() - for reading or writing component state:

+ +

The handler receives *zx.client.Event.Stateful. Call e.state(T) in the same order as ctx.state() in the component to access each piece of state.

+

3. State transform via state.bind() - the simplest option when the update is a pure function of the current value:

+ +

The transform function takes the current value and returns the new value. No event object is needed.

+ +

React integration

+

Use @rendering={"{.react}"} to embed a React or TSX component. Import the component with @jsImport and pass it as you would a ZX component. React handles rendering and hydration on the client.

Attribute Syntax