Actions & Events
Handle user interactions
Action Definition
Actions define how your application responds to events. Each action has a name and an array of steps.
{
"actions": [
{
"name": "submit",
"steps": [
{ "do": "set", "target": "loading", "value": { "expr": "lit", "value": true } },
{ "do": "update", "target": "items", "operation": "push", "value": { "expr": "state", "name": "newItem" } },
{ "do": "set", "target": "newItem", "value": { "expr": "lit", "value": "" } }
]
}
]
}Steps execute sequentially, from first to last.
Action Steps
set - Set State Value
Replace a state value entirely.
{
"do": "set",
"target": "count",
"value": { "expr": "lit", "value": 0 }
}The value field accepts any expression:
{
"do": "set",
"target": "doubled",
"value": {
"expr": "bin",
"op": "*",
"left": { "expr": "state", "name": "count" },
"right": { "expr": "lit", "value": 2 }
}
}update - Modify State
Apply an operation to modify state.
{
"do": "update",
"target": "count",
"operation": "increment"
}Update Operations
increment
Increase a number by 1 (or by a specified amount).
{ "do": "update", "target": "count", "operation": "increment" }
{ "do": "update", "target": "count", "operation": "increment", "value": { "expr": "lit", "value": 5 } }decrement
Decrease a number by 1 (or by a specified amount).
{ "do": "update", "target": "count", "operation": "decrement" }
{ "do": "update", "target": "count", "operation": "decrement", "value": { "expr": "lit", "value": 10 } }push
Add an item to the end of a list.
{
"do": "update",
"target": "items",
"operation": "push",
"value": { "expr": "state", "name": "newItem" }
}pop
Remove the last item from a list.
{ "do": "update", "target": "items", "operation": "pop" }remove
Remove an item from a list by index or by matching a condition.
{
"do": "update",
"target": "items",
"operation": "remove",
"index": { "expr": "var", "name": "index" }
}Note
The remove operation is commonly used inside each loops where the index is available.
toggle
Flip a boolean state value. Perfect for modals, dropdowns, and visibility toggles.
{ "do": "update", "target": "isMenuOpen", "operation": "toggle" }Modal Toggle Example
{
"state": {
"modalOpen": { "type": "boolean", "initial": false }
},
"actions": [
{
"name": "toggleModal",
"steps": [
{ "do": "update", "target": "modalOpen", "operation": "toggle" }
]
}
],
"view": {
"kind": "element",
"tag": "div",
"children": [
{
"kind": "element",
"tag": "button",
"props": { "onClick": { "event": "click", "action": "toggleModal" } },
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "Open Modal" } }
]
},
{
"kind": "if",
"condition": { "expr": "state", "name": "modalOpen" },
"then": {
"kind": "element",
"tag": "div",
"props": { "class": { "expr": "lit", "value": "modal" } },
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "Modal Content" } },
{
"kind": "element",
"tag": "button",
"props": { "onClick": { "event": "click", "action": "toggleModal" } },
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "Close" } }
]
}
]
}
}
]
}
}merge
Shallow-merge properties into an object state. Ideal for form handling where you update individual fields.
{
"do": "update",
"target": "formData",
"operation": "merge",
"value": { "expr": "lit", "value": { "email": "new@example.com" } }
}Form Update Example
{
"state": {
"form": {
"type": "object",
"initial": { "name": "", "email": "", "message": "" }
}
},
"actions": [
{
"name": "updateName",
"steps": [
{
"do": "update",
"target": "form",
"operation": "merge",
"value": {
"expr": "lit",
"value": { "name": "value-from-input" }
}
}
]
}
]
}Tip
The merge operation only overwrites specified properties. Other properties in the object remain unchanged, making it ideal for partial form updates.
replaceAt
Replace an element at a specific index in a list. Used for editing items in place.
{
"do": "update",
"target": "items",
"operation": "replaceAt",
"index": { "expr": "var", "name": "index" },
"value": { "expr": "state", "name": "editedItem" }
}Note
The replaceAt operation is commonly used inside each loops where the index is available, enabling inline editing of list items.
insertAt
Insert an item at a specific index in a list. All elements at and after the index are shifted.
{
"do": "update",
"target": "items",
"operation": "insertAt",
"index": { "expr": "lit", "value": 0 },
"value": { "expr": "state", "name": "newItem" }
}Tip
Use insertAt with index 0 to add items at the beginning of a list, or with the list length to append at the end.
splice
Delete and/or insert items at a specific index. This is the most flexible list operation.
{
"do": "update",
"target": "items",
"operation": "splice",
"index": { "expr": "var", "name": "index" },
"deleteCount": { "expr": "lit", "value": 1 },
"value": { "expr": "lit", "value": ["newItem1", "newItem2"] }
}| Property | Required | Description |
|---|---|---|
index | Yes | Position to start the operation |
deleteCount | Yes | Number of items to delete |
value | No | Array of items to insert at the position |
Note
The splice operation mirrors JavaScript's Array.prototype.splice(). Set deleteCount to 0 to insert without deleting.
Event Handlers
Connect user interactions to actions using event handlers in props.
{
"kind": "element",
"tag": "button",
"props": {
"onClick": { "event": "click", "action": "increment" }
},
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "Click me" } }
]
}Note
Event handlers have the structure { "event": "eventType", "action": "actionName", "payload": ... }. The event field specifies the DOM event type (e.g., "click", "input", "submit").
Supported Events
| Event | Description | Common Use |
|---|---|---|
onClick | Element clicked | Buttons, links |
onInput | Input value changed | Text inputs |
onSubmit | Form submitted | Forms |
onChange | Value changed | Select, checkbox |
onFocus | Element focused | Inputs |
onBlur | Element lost focus | Inputs |
onClick
Triggered when an element is clicked.
{
"kind": "element",
"tag": "button",
"props": {
"onClick": { "event": "click", "action": "handleClick" }
}
}onInput
Triggered when an input's value changes. Use var expression to access the value.
{
"kind": "element",
"tag": "input",
"props": {
"type": { "expr": "lit", "value": "text" },
"value": { "expr": "state", "name": "inputValue" },
"onInput": { "event": "input", "action": "updateInput" }
}
}With the action:
{
"actions": [
{
"name": "updateInput",
"steps": [
{
"do": "set",
"target": "inputValue",
"value": { "expr": "var", "name": "event", "path": "target.value" }
}
]
}
]
}Tip
For input events, event.target.value contains the current input value. Access it with { "expr": "var", "name": "event", "path": "target.value" }.
onSubmit
Triggered when a form is submitted.
{
"kind": "element",
"tag": "form",
"props": {
"onSubmit": { "event": "submit", "action": "handleSubmit" }
},
"children": [
{
"kind": "element",
"tag": "input",
"props": {
"type": { "expr": "lit", "value": "text" }
}
},
{
"kind": "element",
"tag": "button",
"props": {
"type": { "expr": "lit", "value": "submit" }
},
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "Submit" } }
]
}
]
}Complete Example: Enhanced Todo List
Here's a complete example combining state, actions, events, and the new features:
{
"version": "1.0",
"state": {
"todos": {
"type": "list",
"initial": [
{ "id": 1, "title": "Learn Constela", "done": false },
{ "id": 2, "title": "Build an app", "done": false }
]
},
"newTodo": { "type": "string", "initial": "" }
},
"actions": [
{
"name": "updateNewTodo",
"steps": [
{
"do": "set",
"target": "newTodo",
"value": { "expr": "var", "name": "event", "path": "target.value" }
}
]
},
{
"name": "addTodo",
"steps": [
{
"do": "update",
"target": "todos",
"operation": "push",
"value": {
"expr": "lit",
"value": { "id": 0, "title": "", "done": false }
}
},
{
"do": "set",
"target": "newTodo",
"value": { "expr": "lit", "value": "" }
}
]
},
{
"name": "toggleDone",
"steps": [
{
"do": "update",
"target": "todos",
"operation": "replaceAt",
"index": { "expr": "var", "name": "index" },
"value": {
"expr": "lit",
"value": { "done": true }
}
}
]
},
{
"name": "removeTodo",
"steps": [
{
"do": "update",
"target": "todos",
"operation": "remove",
"index": { "expr": "var", "name": "index" }
}
]
}
],
"view": {
"kind": "element",
"tag": "div",
"children": [
{
"kind": "element",
"tag": "input",
"props": {
"type": { "expr": "lit", "value": "text" },
"value": { "expr": "state", "name": "newTodo" },
"placeholder": { "expr": "lit", "value": "Enter a todo..." },
"onInput": { "event": "input", "action": "updateNewTodo" }
}
},
{
"kind": "element",
"tag": "button",
"props": { "onClick": { "event": "click", "action": "addTodo" } },
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "Add" } }
]
},
{
"kind": "each",
"items": { "expr": "state", "name": "todos" },
"as": "todo",
"index": "index",
"body": {
"kind": "element",
"tag": "div",
"props": { "class": { "expr": "lit", "value": "todo-item" } },
"children": [
{
"kind": "text",
"value": {
"expr": "get",
"base": { "expr": "var", "name": "todo" },
"path": "title"
}
},
{
"kind": "text",
"value": {
"expr": "cond",
"if": {
"expr": "get",
"base": { "expr": "var", "name": "todo" },
"path": "done"
},
"then": { "expr": "lit", "value": " [Completed]" },
"else": { "expr": "lit", "value": " [Pending]" }
}
},
{
"kind": "element",
"tag": "button",
"props": { "onClick": { "event": "click", "action": "toggleDone" } },
"children": [
{
"kind": "text",
"value": {
"expr": "cond",
"if": {
"expr": "get",
"base": { "expr": "var", "name": "todo" },
"path": "done"
},
"then": { "expr": "lit", "value": "Undo" },
"else": { "expr": "lit", "value": "Complete" }
}
}
]
},
{
"kind": "element",
"tag": "button",
"props": { "onClick": { "event": "click", "action": "removeTodo" } },
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "Remove" } }
]
}
]
}
}
]
}
}This example demonstrates:
getexpression to accesstodo.titleandtodo.donecondexpression for conditional labels ("Completed" vs "Pending")replaceAtoperation for toggling completion statuseachloop with index for item-level operations
Lifecycle Hooks
Lifecycle hooks allow you to execute actions at specific points in a component's or page's lifecycle.
{
"lifecycle": {
"onMount": "initialize",
"onUnmount": "cleanup"
}
}Available Hooks
| Hook | Description |
|---|---|
onMount | Executed when the component/page is mounted to the DOM |
onUnmount | Executed when the component/page is removed from the DOM |
onRouteEnter | Executed when entering a route (navigation) |
onRouteLeave | Executed when leaving a route (navigation) |
Example: Theme Initialization
{
"lifecycle": {
"onMount": "loadTheme"
},
"actions": [
{
"name": "loadTheme",
"steps": [
{
"do": "storage",
"operation": "get",
"key": { "expr": "lit", "value": "theme" },
"storage": "local",
"result": "savedTheme",
"onSuccess": [
{
"do": "if",
"condition": { "expr": "var", "name": "savedTheme" },
"then": [
{ "do": "set", "target": "theme", "value": { "expr": "var", "name": "savedTheme" } }
]
}
]
}
]
}
]
}Tip
Use onMount for initialization tasks like loading saved preferences, setting up external libraries, or fetching initial data. Use onUnmount to clean up resources and prevent memory leaks.
Advanced Actions
For more advanced action steps, see the Actions Reference:
storage- Read/write to localStorage or sessionStoragedom- Direct DOM manipulation (addClass, removeClass, etc.)import- Dynamic module loading from CDNcall- Call functions on imported JavaScript librariessubscribe- Subscribe to events from external objectsdispose- Clean up resources and subscriptionsif- Conditional step execution
Next Steps
Learn how to make HTTP requests with Fetch & Effects.