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.

json
{
  "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.

json
{
  "do": "set",
  "target": "count",
  "value": { "expr": "lit", "value": 0 }
}

The value field accepts any expression:

json
{
  "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.

json
{
  "do": "update",
  "target": "count",
  "operation": "increment"
}

Update Operations

increment

Increase a number by 1 (or by a specified amount).

json
{ "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).

json
{ "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.

json
{
  "do": "update",
  "target": "items",
  "operation": "push",
  "value": { "expr": "state", "name": "newItem" }
}

pop

Remove the last item from a list.

json
{ "do": "update", "target": "items", "operation": "pop" }

remove

Remove an item from a list by index or by matching a condition.

json
{
  "do": "update",
  "target": "items",
  "operation": "remove",
  "index": { "expr": "var", "name": "index" }
}

toggle

Flip a boolean state value. Perfect for modals, dropdowns, and visibility toggles.

json
{ "do": "update", "target": "isMenuOpen", "operation": "toggle" }

Modal Toggle Example

json
{
  "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.

json
{
  "do": "update",
  "target": "formData",
  "operation": "merge",
  "value": { "expr": "lit", "value": { "email": "new@example.com" } }
}

Form Update Example

json
{
  "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" }
          }
        }
      ]
    }
  ]
}

replaceAt

Replace an element at a specific index in a list. Used for editing items in place.

json
{
  "do": "update",
  "target": "items",
  "operation": "replaceAt",
  "index": { "expr": "var", "name": "index" },
  "value": { "expr": "state", "name": "editedItem" }
}

insertAt

Insert an item at a specific index in a list. All elements at and after the index are shifted.

json
{
  "do": "update",
  "target": "items",
  "operation": "insertAt",
  "index": { "expr": "lit", "value": 0 },
  "value": { "expr": "state", "name": "newItem" }
}

splice

Delete and/or insert items at a specific index. This is the most flexible list operation.

json
{
  "do": "update",
  "target": "items",
  "operation": "splice",
  "index": { "expr": "var", "name": "index" },
  "deleteCount": { "expr": "lit", "value": 1 },
  "value": { "expr": "lit", "value": ["newItem1", "newItem2"] }
}
PropertyRequiredDescription
indexYesPosition to start the operation
deleteCountYesNumber of items to delete
valueNoArray of items to insert at the position

Event Handlers

Connect user interactions to actions using event handlers in props.

json
{
  "kind": "element",
  "tag": "button",
  "props": {
    "onClick": { "event": "click", "action": "increment" }
  },
  "children": [
    { "kind": "text", "value": { "expr": "lit", "value": "Click me" } }
  ]
}

Supported Events

EventDescriptionCommon Use
onClickElement clickedButtons, links
onInputInput value changedText inputs
onSubmitForm submittedForms
onChangeValue changedSelect, checkbox
onFocusElement focusedInputs
onBlurElement lost focusInputs

onClick

Triggered when an element is clicked.

json
{
  "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.

json
{
  "kind": "element",
  "tag": "input",
  "props": {
    "type": { "expr": "lit", "value": "text" },
    "value": { "expr": "state", "name": "inputValue" },
    "onInput": { "event": "input", "action": "updateInput" }
  }
}

With the action:

json
{
  "actions": [
    {
      "name": "updateInput",
      "steps": [
        {
          "do": "set",
          "target": "inputValue",
          "value": { "expr": "var", "name": "event", "path": "target.value" }
        }
      ]
    }
  ]
}

onSubmit

Triggered when a form is submitted.

json
{
  "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:

json
{
  "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:

  • get expression to access todo.title and todo.done
  • cond expression for conditional labels ("Completed" vs "Pending")
  • replaceAt operation for toggling completion status
  • each loop 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.

json
{
  "lifecycle": {
    "onMount": "initialize",
    "onUnmount": "cleanup"
  }
}

Available Hooks

HookDescription
onMountExecuted when the component/page is mounted to the DOM
onUnmountExecuted when the component/page is removed from the DOM
onRouteEnterExecuted when entering a route (navigation)
onRouteLeaveExecuted when leaving a route (navigation)

Example: Theme Initialization

json
{
  "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" } }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Advanced Actions

For more advanced action steps, see the Actions Reference:

  • storage - Read/write to localStorage or sessionStorage
  • dom - Direct DOM manipulation (addClass, removeClass, etc.)
  • import - Dynamic module loading from CDN
  • call - Call functions on imported JavaScript libraries
  • subscribe - Subscribe to events from external objects
  • dispose - Clean up resources and subscriptions
  • if - Conditional step execution

Next Steps

Learn how to make HTTP requests with Fetch & Effects.