Skip to main content
zerotal

Documentation


Documentation / @zerotal/admin / index / ResourceFormPage

Class: ResourceFormPage

Defined in: admin/src/pages/ResourceFormPage.tsx:75

Base class for Flow pages — reactive server-side components rendered over a WebSocket.

Remarks

A Flow component's state lives on the server. You subclass Component, hold state in fields, and implement render to return an HtmlNode (authored in .tsx with jsxImportSource: "@zerotal/flow"). The root element must carry data-flow-root.

Only fields and methods the component opts into are reachable from the browser:

  • @expose marks a field as reactive (streamed to the client, writable via flow:model / $flow.$set) or a method as a callable action (invoked over the socket, e.g. flow:click).
  • @locked sends a field to the client read-only — it round-trips in the snapshot but the client cannot mutate it (enforced in $set).

Between requests, the component is serialised into an HMAC-signed snapshot and rehydrated on the next round-trip, so tampering with client-held state is rejected. When an action runs, the server re-renders and streams a DOM diff that the browser applies via Alpine's morph — only the changed HTML crosses the wire.

Lifecycle hooks fire in order around each request: onBoot → (onMount on the initial GET / onHydrate on round-trips) → property updates → the action → onUpdateonRenderingrenderonRenderedonDehydrate. Actions may emit side effects — flashes, redirects, events, downloads, client scripts — which are drained after the action and folded into the response frame.

Examples

A counter with an exposed field and an action, bound reactively:

import { Component, expose } from "@zerotal/flow";

export class Counter extends Component {
  @expose count = 0;

  @expose increment() {
    this.count++;
  }

  override async render() {
    return (
      <div data-flow-root>
        <p>{this.count}</p>
        <button flow:click="increment">+</button>
      </div>
    );
  }
}

A form-style component with validation, a flash, and a redirect from an action:

import { Component, expose, locked } from "@zerotal/flow";

export class ProfilePage extends Component {
  @locked userId = 0;
  @expose name = "";

  @expose async save() {
    await this.validate({ name: (rule) => rule.required().min(2) });
    await User.update(this.userId, { name: this.name });
    return this.redirect("/dashboard").withSuccess("Profile saved.");
  }

  override async render() {
    return (
      <div data-flow-root>
        <input {...this.bind("name")} />
        {this.errors.has("name") && <span>{this.errors.name}</span>}
        <button flow:click="save">Save</button>
      </div>
    );
  }
}

Extends

Constructors

Constructor

new ResourceFormPage(): ResourceFormPage

Returns

ResourceFormPage

Inherited from

Component.constructor

Actions

signal

Get Signature

get signal(): AbortSignal

Defined in: flow/src/Component.ts:447

The AbortSignal for the running @task. Pass it to fetch/an SDK, or check this.signal.aborted in a loop, for cooperative cancellation. Outside a task it is an already-inert signal that never aborts, so @task code reads the same regardless.

Returns

AbortSignal

Inherited from

Component.signal


cancelled

Get Signature

get cancelled(): boolean

Defined in: flow/src/Component.ts:455

True when the running @task has been cancelled from the client.

Returns

boolean

Inherited from

Component.cancelled


flash()

flash(message, optionsOrLevel?): FlashBuilder

Defined in: flow/src/Component.ts:710

Emit a flash notification to the client.

Returns a FlashBuilder so the toast can be configured fluently. The second argument may also be a level string (back-compat) or a FlashOptions object (for dynamic config):

Parameters

message

string

the toast text.

optionsOrLevel?

"error" | "success" | "warning" | "info" | FlashOptions

a FlashLevel shorthand or a FlashOptions object.

Returns

FlashBuilder

a FlashBuilder for fluent configuration.

Example

this.flash("Saved");                              // success toast
this.flash("Bad input", "error");                 // level shorthand
this.flash("Saved", { type: "success", duration }); // options object
this.flash("Post deleted").warning().duration(8000).progressBar();

Inherited from

Component.flash


refresh()

refresh(): Promise<void>

Defined in: flow/src/Component.ts:859

Force onMount() to run again on the CURRENT WebSocket round trip. Use this to reload external data without requiring a full page reload.

Returns

Promise<void>

Inherited from

Component.refresh


client()

client(script): void

Defined in: flow/src/Component.ts:879

Queue a raw JavaScript expression to be evaluated in the browser after the current action's DOM patch is applied.

The expression runs in the Alpine context of the component root element: $el, $refs, and other Alpine magic properties are all available.

Multiple calls are batched and executed in order after the single round trip.

Parameters

script

string

Returns

void

Example

this.client(`$refs.titleInput.focus()`);
this.client(`$el.querySelector('.toast').classList.add('visible')`);

Security

Never interpolate unescaped user input into the expression string.

Inherited from

Component.client


title()

Call Signature

title(): string

Defined in: flow/src/Component.ts:887

Gets the current title value.

Returns

string

Inherited from

Component.title

Call Signature

title(value): void

Defined in: flow/src/Component.ts:897

Update document.title in the browser after this action completes.

Parameters
value

string

Returns

void

Example
this.title(`Search: ${this.query}`);
Inherited from

Component.title


download()

download(filename, content, mime?): void

Defined in: flow/src/Component.ts:1170

Trigger a browser file download.

content is either text (CSV, JSON, an SVG) or the raw bytes of a binary file. Text is encoded as UTF-8; bytes are sent exactly as given, which is what a format like a spreadsheet or a PDF requires — passing those through a string would re-encode every byte above 127 and corrupt the file.

Parameters

filename

string

the download's suggested file name.

content

string | Uint8Array<ArrayBufferLike>

text, or the file's raw bytes (base64-encoded to travel).

mime?

string = "application/octet-stream"

the MIME type. Defaults to application/octet-stream.

Returns

void

Example

async export() {
  const csv = this.buildCsv(this.items);
  this.download('export.csv', csv, 'text/csv;charset=utf-8');
}

Inherited from

Component.download


stream()

stream(ref, content, opts?): void

Defined in: flow/src/Component.ts:1396

Push content to the client MID-ACTION — before the final patch — into any element marked flow:stream="ref". Appends by default; pass { replace: true } to overwrite instead.

Works only during WebSocket actions (no-op during SSR).

Parameters

ref

string

the flow:stream="ref" target element.

content

string

raw HTML appended (or replacing) inside the target.

opts?

pass { replace: true } to overwrite instead of append.

replace?

boolean

Returns

void

Security

content is injected as raw innerHTML on the client. Never pass unescaped user input directly — sanitize or escape it first, or use a trusted HTML sanitizer (e.g. DOMPurify) client-side.

Example


Expose

async generate() { for await (const token of llm.stream(this.prompt)) { this.stream('answer', token); } }

Inherited from

Component.stream

Errors

onError()

onError(error): Promise<void>

Defined in: flow/src/Component.ts:591

Called when an action throws an unhandled error. Flow catches the error and still re-renders; override to customise (log, swallow, re-flash). The default flashes the message. (Livewire exception().)

Parameters

error

Error

the error thrown by the action.

Returns

Promise<void>

Inherited from

Component.onError

Events

dispatch()

dispatch<K>(name, ...args): void

Defined in: flow/src/Component.ts:1057

Dispatch a named event to all other Flow components on the page. Components with a matching @on('event-name') decorator will have their listener method called automatically.

Type Parameters

K

K extends string

Parameters

name

K

args

...K extends never ? EventArgs<FlowEvents[K]> : [Record<string, unknown>]

Returns

void

Example

async save() {
  const post = await Post.create(this.form);
  this.dispatch('post-created', { id: post.id });
}

Inherited from

Component.dispatch


dispatchTo()

dispatchTo<K>(component, name, ...args): void

Defined in: flow/src/Component.ts:1075

Dispatch an event ONLY to components of a given class name (skips all others listening for the same event). Mirrors Livewire's dispatch()->to(...).

Type Parameters

K

K extends string

Parameters

component

string

name

K

args

...K extends never ? EventArgs<FlowEvents[K]> : [Record<string, unknown>]

Returns

void

Example

this.dispatchTo('Dashboard', 'post-created', { id: post.id });

Inherited from

Component.dispatchTo


dispatchSelf()

dispatchSelf<K>(name, ...args): void

Defined in: flow/src/Component.ts:1094

Dispatch an event only to THIS component (it won't bubble to others). Mirrors Livewire's dispatch()->self().

Type Parameters

K

K extends string

Parameters

name

K

args

...K extends never ? EventArgs<FlowEvents[K]> : [Record<string, unknown>]

Returns

void

Example

this.dispatchSelf('refresh');

Inherited from

Component.dispatchSelf

Lifecycle

onMount()

onMount(ctx?): Promise<void>

Defined in: admin/src/pages/ResourceFormPage.tsx:240

Called ONCE on the initial HTTP GET request. Never called on subsequent WebSocket round trips (call this.refresh() to force it).

Receives the route HttpContext — the same argument controllers get — so dynamic-segment pages can read implicitly-bound models off ctx.params:

override async onMount(ctx: HttpContext) {
  this.accountId = (ctx.params.account as Account).id;
}

Bound models persist across WebSocket round-trips via Flow synths (store the model on a @locked/@expose field), so they need not be re-resolved on every action.

Parameters

ctx?

HttpContext<Record<string, string>>

the route HttpContext (initial GET only).

Returns

Promise<void>

Overrides

Component.onMount


onUpdate()

onUpdate(): Promise<void>

Defined in: flow/src/Component.ts:510

Called once after the invoked action method completes, just before re-rendering. Distinct from onUpdated(), which fires per client-driven property change.

Returns

Promise<void>

Inherited from

Component.onUpdate


onBoot()

onBoot(_ctx?): Promise<void>

Defined in: flow/src/Component.ts:523

Called at the BEGINNING of every request for this component — both the initial HTTP render and every subsequent WebSocket round-trip — after state is restored (round-trips) or @url/@session seeded (initial), but before onMount() / onHydrate(), property updates, and the action. Use for setup that must run on every request. (Livewire boot().) Receives the route HttpContext — note that on WebSocket round-trips the URL is the stored route pattern, so raw ctx.params are only populated on the initial GET; prefer onMount() for reading bound models.

Parameters

_ctx?

HttpContext<Record<string, string>>

Returns

Promise<void>

Inherited from

Component.onBoot


onHydrate()

onHydrate(): Promise<void>

Defined in: flow/src/Component.ts:533

Called at the beginning of every SUBSEQUENT (WebSocket) request, right after the component is rebuilt from its snapshot — never on the initial render (use onMount() for that). Ideal for re-deriving non-persisted/protected state from restored properties. (Livewire hydrate().)

Returns

Promise<void>

Inherited from

Component.onHydrate


onDehydrate()

onDehydrate(): Promise<void>

Defined in: flow/src/Component.ts:542

Called at the END of every request, just before the component is serialised into its snapshot. Use to normalise state back into a serialisable shape. (Livewire dehydrate().)

Returns

Promise<void>

Inherited from

Component.onDehydrate


onUpdating()

onUpdating(_prop, _value, _key?): Promise<void>

Defined in: flow/src/Component.ts:556

Called BEFORE a client-driven property update is applied (a value/checked input or $flow.$set). Throw to reject the update. Only fires for writable (@expose) properties. For a single property, define onUpdating<Prop>(value, key?) — e.g. onUpdatingEmail(). (Livewire updating().)

Parameters

_prop

string

_value

unknown

_key?

string

Returns

Promise<void>

Inherited from

Component.onUpdating


onRendering()

onRendering(): Promise<void>

Defined in: flow/src/Component.ts:573

Called BEFORE render() runs. (Livewire rendering().)

Returns

Promise<void>

Inherited from

Component.onRendering


onRendered()

onRendered(_html): Promise<void>

Defined in: flow/src/Component.ts:581

Called AFTER render() produces this component's HTML. (Livewire rendered().)

Parameters

_html

string

Returns

Promise<void>

Inherited from

Component.onRendered

redirect()

redirect(url, status?): RedirectFlash

Defined in: flow/src/Component.ts:781

Redirect the client to url after the current action completes.

Returns a builder so a flash can be chained (mirrors the HTTP ResponseBuilder):

Parameters

url

string

Destination URL.

status?

number

Optional HTTP-style status (advisory — the client navigates regardless).

Returns

RedirectFlash

a RedirectFlash builder for chaining a flash onto the redirect.

Example

this.redirect("/dashboard");
return this.redirect(next || "/", 303).withSuccess("Welcome back.");
return this.redirect("/login").withError("Please sign in.");

Inherited from

Component.redirect


redirectRoute()

redirectRoute(name, params?, status?): RedirectFlash

Defined in: flow/src/Component.ts:820

Redirect to a named route, mirroring Livewire's redirectRoute('profile', ['id' => 1]). Route params fill :segment placeholders; any extra keys become query-string params.

Parameters

name

string

The route name registered with .name(...).

params?

Record<string, string | number> = {}

Route + query params.

status?

number

Optional HTTP-style status (advisory — the client navigates regardless).

Returns

RedirectFlash

a RedirectFlash builder for chaining a flash onto the redirect.

Example

return this.redirectRoute("profile", { id: 1 }).withSuccess("Saved.");
this.redirectRoute("posts.show", { slug: "hello", ref: "email" }); // /posts/hello?ref=email

Inherited from

Component.redirectRoute


redirectIntended()

redirectIntended(fallback?, status?): RedirectFlash

Defined in: flow/src/Component.ts:842

Redirect the user back to where they were headed before being intercepted — Livewire's redirectIntended('/default/url'). Reads (and clears) the intended_url stored in the session by AuthMiddleware, falling back to fallback when none is stored or the stored URL is cross-origin (open-redirect guard).

Parameters

fallback?

string = "/"

URL to use when no intended URL is available. Defaults to /.

status?

number

Optional HTTP-style status (advisory — the client navigates regardless).

Returns

RedirectFlash

a RedirectFlash builder for chaining a flash onto the redirect.

Example

if (await Auth.attempt(creds)) return this.redirectIntended("/dashboard");

Inherited from

Component.redirectIntended


currentUrl()

currentUrl(_options?): string

Defined in: flow/src/Component.ts:1133

Build a URL from the current one with merged query params — client-only. Use it in a JSX expression: href={this.currentUrl({ query: this.q })}, class={this.currentUrl() === "/" ? "on" : ""}, or {this.currentUrl(...)}. The compiler rewrites this.currentUrl$flow.currentUrl so it runs on the client. It never runs on the server — hitting this throw means the call reached server code (e.g. a page the compiler couldn't statically compile, or a server action).

Parameters

_options?

CurrentUrlOptions

Returns

string

Throws

when executed on the server (it is meant to be compiled to $flow.currentUrl).

Inherited from

Component.currentUrl


navigateCurrent(_options?): Promise<void>

Defined in: flow/src/Component.ts:1145

Build the URL as currentUrl does, then SPA-navigate to it — client-only. Use it in a client handler: onClick={() => this.navigateCurrent({ query: { page: 2 } })}. The compiler rewrites it to $flow.navigateCurrent. To navigate from server code, return a redirect().

Parameters

_options?

CurrentUrlOptions

Returns

Promise<void>

Throws

when executed on the server (it is meant to be compiled to $flow.navigateCurrent).

Inherited from

Component.navigateCurrent

Other

Get Signature

get static head(): string

Defined in: admin/src/pages/ResourceFormPage.tsx:160

Stylesheet + theme tokens for the panel owning this request.

Returns

string


slug

slug: string = ""

Defined in: admin/src/pages/ResourceFormPage.tsx:76


mode

mode: FieldMode = "create"

Defined in: admin/src/pages/ResourceFormPage.tsx:77


recordId

recordId: string = ""

Defined in: admin/src/pages/ResourceFormPage.tsx:78


panelId

panelId: string = DEFAULT_PANEL_ID

Defined in: admin/src/pages/ResourceFormPage.tsx:84

Which panel is being served. Locked rather than derived, so the WebSocket round-trips that drive this page — which carry no URL — keep resolving the same resource, base path and shell as the initial render.


parentId

parentId: string = ""

Defined in: admin/src/pages/ResourceFormPage.tsx:86

The parent record's id, for a resource nested under another.


loadedVersion

loadedVersion: string = ""

Defined in: admin/src/pages/ResourceFormPage.tsx:92

The record's version as it was when this form loaded, for a resource using optimisticLock. Locked, so a WebSocket save compares against what was actually rendered rather than whatever the client claims.


formLocale

formLocale: string = ""

Defined in: admin/src/pages/ResourceFormPage.tsx:94

The locale being edited, for a resource with translatable fields.


translations

translations: Record<string, Record<string, unknown>> = {}

Defined in: admin/src/pages/ResourceFormPage.tsx:102

Every locale's value for each translatable field, as the record held them.

Kept because the form only ever shows one locale: without the rest, saving an English edit would write { en: "…" } over a record that also had French and German. Locked so the client cannot rewrite the locales it isn't editing.


form

form: Form & Record<string, unknown>

Defined in: admin/src/pages/ResourceFormPage.tsx:109


switchLocale()

switchLocale(code): void

Defined in: admin/src/pages/ResourceFormPage.tsx:124

Switch the locale being edited.

The current locale's text is banked into the translation map first, so switching tabs mid-edit does not throw the work away, then the new locale's text is loaded into the same fields.

Parameters

code

unknown

Returns

void


layout()

layout(page): Promise<HtmlNode>

Defined in: admin/src/pages/ResourceFormPage.tsx:155

The shell. Rendered through the instance hook rather than static layout, because one class serves every panel and the shell differs per panel — the layout marks its own identity so the client still swaps only the content slot when navigating within a panel.

Parameters

page

HtmlNode

Returns

Promise<HtmlNode>

Overrides

Component.layout


setField()

setField(key, value): void

Defined in: admin/src/pages/ResourceFormPage.tsx:302

Set a single field's value from the server (radio / native-control fallbacks).

Parameters

key

unknown

value

unknown

Returns

void


toggleArrayValue()

toggleArrayValue(key, value): void

Defined in: admin/src/pages/ResourceFormPage.tsx:307

Toggle a value in an array-valued field (checkbox list / multiple select).

Parameters

key

unknown

value

unknown

Returns

void


tagDraft

tagDraft: Record<string, string> = {}

Defined in: admin/src/pages/ResourceFormPage.tsx:315

Draft text for each tags input, keyed by field.


addTag()

addTag(key): void

Defined in: admin/src/pages/ResourceFormPage.tsx:318

Commit the current tag draft into a tags field's array.

Parameters

key

unknown

Returns

void


removeTag()

removeTag(key, index): void

Defined in: admin/src/pages/ResourceFormPage.tsx:329

Remove a tag by index.

Parameters

key

unknown

index

unknown

Returns

void


pickingFor

pickingFor: string = ""

Defined in: admin/src/pages/ResourceFormPage.tsx:338

The field a media picker is open for, or empty when it is closed.


pickerSearch

pickerSearch: string = ""

Defined in: admin/src/pages/ResourceFormPage.tsx:339


mediaUpload

mediaUpload: unknown = null

Defined in: admin/src/pages/ResourceFormPage.tsx:348

A file chosen from inside the picker.

Held on the page rather than on the form, because the form's exposed properties are generated from the resource's declared fields — an extra key on it is not part of the snapshot, so the upload's reference would be dropped on the way back and the file would silently never arrive.


openMediaPicker()

openMediaPicker(key): void

Defined in: admin/src/pages/ResourceFormPage.tsx:430

Parameters

key

unknown

Returns

void


closeMediaPicker()

closeMediaPicker(): void

Defined in: admin/src/pages/ResourceFormPage.tsx:435

Returns

void


chooseMedia()

chooseMedia(path): void

Defined in: admin/src/pages/ResourceFormPage.tsx:440

Choose a library file for the field the picker was opened from.

Parameters

path

unknown

Returns

void


onUpdated()

onUpdated(prop, _value): Promise<void>

Defined in: admin/src/pages/ResourceFormPage.tsx:456

Store the file once its bytes have actually arrived.

Driven by the property update rather than the input's change event, and the difference is the whole bug: a bound file input starts an HTTP upload on change and only sets the signed reference when that finishes. An action fired from change therefore ran while the property was still empty, found nothing to store, and returned silently — the file picker appeared to do nothing at all.

Parameters

prop

string

_value

unknown

Returns

Promise<void>

Overrides

Component.onUpdated


uploadToLibrary()

uploadToLibrary(): Promise<void>

Defined in: admin/src/pages/ResourceFormPage.tsx:461

Upload straight into the library from the picker, and select the result.

Returns

Promise<void>


removeFile()

removeFile(key): void

Defined in: admin/src/pages/ResourceFormPage.tsx:481

Parameters

key

unknown

Returns

void


repeaterDraft

repeaterDraft: Record<string, unknown> = {}

Defined in: admin/src/pages/ResourceFormPage.tsx:493

Flat per-sub-input draft for repeater/builder rows.


addRepeaterItem()

addRepeaterItem(key): void

Defined in: admin/src/pages/ResourceFormPage.tsx:517

Append an empty repeater row (sub-fields seeded to their defaults).

Parameters

key

unknown

Returns

void


addBuilderBlock()

addBuilderBlock(key, blockName): void

Defined in: admin/src/pages/ResourceFormPage.tsx:533

Append a builder block of the given type (its fields seeded to defaults).

Parameters

key

unknown

blockName

unknown

Returns

void


removeRepeaterItem()

removeRepeaterItem(key, rowId): void

Defined in: admin/src/pages/ResourceFormPage.tsx:551

Remove a repeater/builder row by id (and drop its drafts).

Parameters

key

unknown

rowId

unknown

Returns

void


moveRepeaterItem()

moveRepeaterItem(key, rowId, dir): void

Defined in: admin/src/pages/ResourceFormPage.tsx:564

Move a repeater/builder row up (-1) or down (+1).

Parameters

key

unknown

rowId

unknown

dir

unknown

Returns

void


fieldChanged()

fieldChanged(key): void

Defined in: admin/src/pages/ResourceFormPage.tsx:607

Run a live field's afterStateUpdated hook and merge its patch into the form.

Parameters

key

unknown

Returns

void


wizardStepIndex

wizardStepIndex: number = 0

Defined in: admin/src/pages/ResourceFormPage.tsx:622

Current wizard step index.


nextStep()

nextStep(): void

Defined in: admin/src/pages/ResourceFormPage.tsx:649

Validate the current step's fields, then advance.

Returns

void


prevStep()

prevStep(): void

Defined in: admin/src/pages/ResourceFormPage.tsx:666

Returns

void


wizardSubmit()

wizardSubmit(): Promise<void>

Defined in: admin/src/pages/ResourceFormPage.tsx:671

Form submit inside a wizard: advance a step, or save on the last one.

Returns

Promise<void>


save()

save(): Promise<void>

Defined in: admin/src/pages/ResourceFormPage.tsx:678

Returns

Promise<void>

Rendering

render()

render(): Promise<HtmlNode>

Defined in: admin/src/pages/ResourceFormPage.tsx:1679

Return the JSX that represents this page. Must include a single root element with data-flow-root.

Returns

Promise<HtmlNode>

the rendered HtmlNode tree for this component.

Overrides

Component.render


placeholder()

placeholder(): HtmlNode

Defined in: flow/src/Component.ts:617

Return the placeholder HTML shown while a lazy/deferred component loads. Override this in child components that use { lazy: true } or { defer: true }.

Returns

HtmlNode

Example

override placeholder() {
  return <div class="skeleton animate-pulse h-24 w-full rounded-lg" />;
}

Inherited from

Component.placeholder


slot()

slot(name?): HtmlNode

Defined in: flow/src/Component.ts:677

Return the HTML the parent passed for a named slot, for placing inside this component's render(). Call with no argument for the default slot (the child's plain children), or a name for a named slot (slots={{ header: … }} on the parent). Returns an empty node when the slot wasn't provided, so {this.slot("footer")} is safe to leave in the template unconditionally.

Slot content is rendered in the PARENT's scope and carried in this component's snapshot, so it survives the child's own round-trips. Use hasSlot() to branch on whether a slot was supplied (e.g. to omit a wrapping <header> entirely).

Parameters

name?

string = "default"

the slot name; omit for the default slot.

Returns

HtmlNode

the slot's HTML, or an empty node when the slot was not provided.

Example

override async render() {
  return (
    <div class="card">
      {this.hasSlot("header") && <header>{this.slot("header")}</header>}
      <div class="body">{this.slot()}</div>
      {this.hasSlot("footer") && <footer>{this.slot("footer")}</footer>}
    </div>
  );
}

Inherited from

Component.slot


hasSlot()

hasSlot(name?): boolean

Defined in: flow/src/Component.ts:685

True when the parent supplied (non-empty) content for the given slot.

Parameters

name?

string = "default"

Returns

boolean

Inherited from

Component.hasSlot


child()

child<C>(ChildClass, opts?): Promise<HtmlNode>

Defined in: flow/src/Component.ts:1233

Embed another Component as a nested component with its own isolated state, its own WebSocket update cycle, and its own snapshot.

Children are islands: a parent re-render does NOT re-render existing children (their DOM and state are preserved on the client), and a child update never touches the parent. Use key when embedding the same component class multiple times (e.g. in a loop).

props are assigned to the child instance before onMount() runs.

Pass lazy: true to use IntersectionObserver — onMount() is deferred until the placeholder enters the viewport. Pass defer: true to load immediately after page paint (no intersection check).

Type Parameters

C

C extends Component

Parameters

ChildClass

() => C

the child Component class to embed.

opts?

key disambiguates repeated instances; props seed the child before onMount(); lazy/defer render a placeholder first; slots pass named slot HTML.

key?

string | number

props?

Partial<C>

lazy?

boolean

defer?

boolean

slots?

Record<string, string>

Named slot HTML (name → html, default slot keyed "default"), rendered by the parent.

Returns

Promise<HtmlNode>

the child's rendered HtmlNode (root or placeholder).

Example

override async render() {
  return <div>
    <h1>Dashboard</h1>
    <StatsWidget />
    <CounterWidget key="a" step={5} />
    <SlowWidget lazy />
  </div>;
}

Inherited from

Component.child

State & exposure

durable?

static optional durable?: boolean | { ttl?: string; scope?: "session" | "user"; }

Defined in: flow/src/Component.ts:467

Opt into durable/resumable snapshots: static durable = true (or { ttl, scope }). The signed snapshot is persisted server-side after every request, keyed by user (or session) + route, and restored on a fresh GET so the user resumes exactly. See clearDurable to drop the stored state on flow completion.

Inherited from

Component.durable


clearDurable()

clearDurable(): void

Defined in: flow/src/Component.ts:479

Forget this component's durable snapshot at the end of the current request — call it when a durable flow completes (a wizard finished, a form submitted) so the next visit starts fresh instead of resuming the finished state. No-op unless the component opted into static durable.

Returns

void

Inherited from

Component.clearDurable


bind()

bind(key): Record<string, unknown>

Defined in: flow/src/Component.ts:1192

Emit the HTML attributes needed for flow:model two-way binding.

Parameters

key

string

the @expose property to two-way bind.

Returns

Record<string, unknown>

the flow:model + value attributes to spread onto an input.

Example

<input {...this.bind('name')} />
// → <input flow:model="name" value="Alice" />

Inherited from

Component.bind

Validation

errors

Get Signature

get errors(): ErrorsProxy

Defined in: flow/src/Component.ts:384

Typed access to this component's validation error bag.

  • this.errors.has("field")boolean
  • this.errors.field → an ErrorField for error={this.errors.field}
  • this.errors.add("field", "message") → add an error manually
  • this.errors.add({ field: "message" }) → add several at once
  • this.errors.clear(field?) → clear one field, or all
Returns

ErrorsProxy

Inherited from

Component.errors


validate()

validate(rulesOrForm?): Promise<void>

Defined in: flow/src/Component.ts:937

Validate page properties against rules. If validation fails, errors are stored and a ValidationError is thrown (caught by the framework, which re-renders the page with $errors populated client-side).

Pass explicit rules or omit to use @validate decorator rules.

Error persistence: validation errors survive across actions and are re-sent on every patch until the next validate() call clears them (on success) or resetValidation() is called explicitly. If you want errors cleared at the start of every action, call this.resetValidation() before the new validate() call.

Parameters

rulesOrForm?

ValidationRules | { __isFlowForm: true; _validateToErrors: Record<string, string[]>; }

explicit per-field rules, a Form instance to validate, or omit to use the component's @validate decorator rules.

Returns

Promise<void>

Example

async save() {
  this.validate({
    email:    (rule) => rule.required().email(),
    password: (rule) => rule.required().min(8),
  });
  // reaches here only if valid
}

Throws

when validation fails — the framework catches it and re-renders with this.errors populated.

Inherited from

Component.validate


addError()

addError(field, message): void

Defined in: flow/src/Component.ts:1020

Manually add a validation error for a field.

Parameters

field

string

message

string

Returns

void

Example

this.addError('email', 'That email is already taken.');

Inherited from

Component.addError


resetValidation()

resetValidation(field?): void

Defined in: flow/src/Component.ts:1034

Clear validation errors — all fields, or just the specified field.

Parameters

field?

string

Returns

void

Example

this.resetValidation();          // clear all
this.resetValidation('email');   // clear email only

Inherited from

Component.resetValidation