Documentation / @zerotal/admin / index / SearchPage
Class: SearchPage
Defined in: admin/src/pages/SearchPage.tsx:16
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:
@exposemarks a field as reactive (streamed to the client, writable viaflow:model/$flow.$set) or a method as a callable action (invoked over the socket, e.g.flow:click).@lockedsends 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 → onUpdate → onRendering → render → onRendered → onDehydrate. 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 SearchPage():
SearchPage
Returns
SearchPage
Inherited from
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
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
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
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
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
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
title()
Call Signature
title():
string
Defined in: flow/src/Component.ts:887
Gets the current title value.
Returns
string
Inherited from
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
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
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
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
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
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
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
Lifecycle
onMount()
onMount(
_ctx?):Promise<void>
Defined in: flow/src/Component.ts:502
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>
Inherited from
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
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
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
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
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
onUpdated()
onUpdated(
_prop,_value,_key?):Promise<void>
Defined in: flow/src/Component.ts:566
Called AFTER a client-driven property update is applied. For a single property,
define onUpdated<Prop>(value, key?) — e.g. onUpdatedUsername() to normalise a
value such as lower-casing. (Livewire updated().)
Parameters
_prop
string
_value
unknown
_key?
string
Returns
Promise<void>
Inherited from
onRendering()
onRendering():
Promise<void>
Defined in: flow/src/Component.ts:573
Called BEFORE render() runs. (Livewire rendering().)
Returns
Promise<void>
Inherited from
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
Navigation & redirects
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
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
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
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
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
a RedirectFlash builder for chaining a flash onto the redirect.
Example
if (await Auth.attempt(creds)) return this.redirectIntended("/dashboard");
Inherited from
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
navigateCurrent()
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
Other
layout
staticlayout: typeofAdminLayout=AdminLayout
Defined in: admin/src/pages/SearchPage.tsx:17
panel
staticpanel:PanelInstance
Defined in: admin/src/pages/SearchPage.tsx:19
The panel this page belongs to — set by each generated subclass.
q
q:
string=""
Defined in: admin/src/pages/SearchPage.tsx:21
Rendering
render()
render():
Promise<HtmlNode>
Defined in: admin/src/pages/SearchPage.tsx:27
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
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
Example
override placeholder() {
return <div class="skeleton animate-pulse h-24 w-full rounded-lg" />;
}
Inherited from
layout()
Defined in: flow/src/Component.ts:647
Wrap the rendered page root in a layout shell. page is the already-rendered
<div data-flow-root>…</div> node; return whatever JSX wraps it. The default is
no layout (returns page unchanged).
This is the JSX-native alternative to static layout = SomeLayout: a layout is
just a component you wrap the page in, and its regions are ordinary props — the
same Page.layout = (page) => <AppLayout>{page}</AppLayout> convention the
framework's React/Inertia pages already use. static layout still works; when
both are present, this method wins.
The shell renders once (on the initial GET) and stays outside the reactive root, so it is not re-rendered or re-sent on WebSocket actions and survives SPA navigation between pages that wrap with the same layout.
Parameters
page
the already-rendered <div data-flow-root> page node to wrap.
Returns
the wrapping shell (or page unchanged for no layout).
Example
override layout(page: HtmlNode) {
return <AppLayout title={ProfilePage.title} actions={<Save />}>{page}</AppLayout>;
}
Inherited from
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
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
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
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
State & exposure
durable?
staticoptionaldurable?: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
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
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
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")→booleanthis.errors.field→ an ErrorField forerror={this.errors.field}this.errors.add("field", "message")→ add an error manuallythis.errors.add({ field: "message" })→ add several at oncethis.errors.clear(field?)→ clear one field, or all
Returns
Inherited from
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
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
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