Skip to content

Functions & Bindings

let still handles values and reusable functions. The action keyword introduces reusable action-shaped records, and component is for element-style declarations that need emitted actions or persistent local state.

type User = { name:string avatarUrl:string }
let <UserCard user:User tone:string = "neutral"/> =
<div className={"card " + tone}>
<img src={user.avatarUrl}/>
<h3>{user.name}</h3>
</div>
let currentUser = <User name="Ada" avatarUrl="/ada.png"/>
<UserCard user={currentUser} tone="info"/>
  • Attributes in the signature declare names, types, and defaults.
  • Invocation mirrors the signature: supply values instead of types.
action SearchSubmitted = {
searchString:string
}
action DoSearch = {
search:string
}
component <SearchBox
placeholder:string
emits {
ValueChanged {
value:string
}
SearchSubmitted
}
/> = {
state {
query:string = {placeholder}
}
<TextInput value={query} placeholder={placeholder} />
}
action TrackSearch = {
value:string
}
let makeValueChanged(value:string): SearchBox.ValueChanged =
<SearchBox.ValueChanged value={value} />
<SearchBox
placeholder="Find docs"
onSearchSubmitted=<DoSearch search={action.searchString}/>
onValueChanged=<TrackSearch value={action.value}/> />
  • Use action for shared action contracts that multiple components can emit.
  • Use component when the declaration needs emits or state.
  • emits can declare a new action inline or reference an existing action by name.
  • Inline emitted actions become public action names such as SearchBox.ValueChanged.
  • Component invocation sites can bind emitted actions through on<ActionName> properties with an implicit action value inside the handler body.
  • Hosts initialize components by name, receive the rendered output plus an opaque state snapshot, and later dispatch ordered action batches with that saved snapshot.
  • State defaults run only during initialization. After that a handler changes state by returning the component’s update record, written <Update ... /> inside the component; it names only the fields that change.
  • Inside a component a handler may return its own Update or an action the component emits; at the root any action or update record goes to the host.
external component <Button label:string emits { Tapped { } } />
component <Counter /> = {
state { count:int = 0 }
<Row>
<Label text={count} />
<Button label="Add" onTapped=<Update count={count + 1} /> />
</Row>
}

Every record also has an update record, so the same patch shape works outside components: <User.Update email={} /> clears email — the field is present and empty, which is allowed only for an optional field such as email?:string — and leaves every other field alone. See Updating state.

A field can also be named as a value. User.Property.email is a case of the derived constant union User.Property, so a sort key or a column list is typed by the fields that exist, and inside a component a bare Property names the component’s own. Four intrinsics work on any update record: apply(record, update), merge(first, second), diff(before, after), and changed(update), which lists the present fields as User.Property cases. See Property references.

let formatName(name:string, title?:string): string = {
if title? { title + " " + name } else { name }
}
let displayName = { formatName("Ada", "Dr.") }
let plainName = { formatName("Ada") }

Use this form for small, general-purpose helpers that compute a value and read naturally with parentheses, such as min, floor or rgb. A function that produces markup uses the element style, even when it takes a single parameter. A paren-style function can also be called as an element, <formatName name="Ada" />; an element-style function is called only as an element.

Parameters a caller may leave out — optional (title?:string) or defaulted (sep:string = ", ") — come last, and a call may stop before them. The function fills each one it is not given: with its default, which it evaluates itself, or with {}.

let primaryTone = "info"
let spacing = 12

let binds any expression to a name; use explicit types when inference isn’t obvious.