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
  • delay - Execute steps after a delay
  • interval - Execute action periodically
  • clearTimer - Stop a running timer
  • focus - Focus, blur, or select form elements

Timer Actions

Execute actions with time delays or at regular intervals.

delay

Execute steps after a specified delay:

json
{
  "do": "delay",
  "ms": { "expr": "lit", "value": 3000 },
  "then": [
    { "do": "set", "target": "notification", "value": { "expr": "lit", "value": "" } }
  ]
}

interval

Execute an action repeatedly:

json
{
  "do": "interval",
  "ms": { "expr": "lit", "value": 5000 },
  "action": "fetchLatestData",
  "result": "pollingTimerId"
}

clearTimer

Stop a running timer:

json
{ "do": "clearTimer", "target": { "expr": "state", "name": "pollingTimerId" } }

Event Handler Options

Debounce

Wait for a pause in events before executing. Ideal for search inputs:

json
{
  "onInput": {
    "event": "input",
    "action": "search",
    "debounce": 300
  }
}

Throttle

Limit execution frequency. Ideal for scroll or resize handlers:

json
{
  "onScroll": {
    "event": "scroll",
    "action": "trackScroll",
    "throttle": 100
  }
}

IntersectionObserver

Detect when an element becomes visible:

json
{
  "onIntersect": {
    "event": "intersect",
    "action": "loadMore",
    "options": {
      "threshold": 0.5,
      "rootMargin": "100px"
    }
  }
}

Event Data

Event handlers have access to various data depending on the event type.

Input Events

json
{ "expr": "var", "name": "value" }    // Input value
{ "expr": "var", "name": "checked" }  // Checkbox checked state

Keyboard Events

json
{ "expr": "var", "name": "key" }      // Key pressed (e.g., "Enter", "a")
{ "expr": "var", "name": "code" }     // Physical key code
{ "expr": "var", "name": "ctrlKey" }  // Ctrl key held
{ "expr": "var", "name": "shiftKey" } // Shift key held
{ "expr": "var", "name": "altKey" }   // Alt key held
{ "expr": "var", "name": "metaKey" }  // Command/Windows key held

Mouse Events

json
{ "expr": "var", "name": "clientX" }  // X position in viewport
{ "expr": "var", "name": "clientY" }  // Y position in viewport
{ "expr": "var", "name": "pageX" }    // X position in document
{ "expr": "var", "name": "pageY" }    // Y position in document
{ "expr": "var", "name": "button" }   // Mouse button (0=left, 1=middle, 2=right)

Touch Events

json
{ "expr": "var", "name": "touches" }  // Array of touch points

Each touch point contains clientX, clientY, pageX, pageY.

Scroll Events

json
{ "expr": "var", "name": "scrollTop" }  // Vertical scroll position
{ "expr": "var", "name": "scrollLeft" } // Horizontal scroll position

File Input Events

json
{ "expr": "var", "name": "files" }  // Array of file info

Each file contains name, size, type.

Example: Keyboard Shortcuts

json
{
  "name": "handleKeydown",
  "steps": [
    {
      "do": "if",
      "condition": {
        "expr": "bin",
        "op": "&&",
        "left": { "expr": "var", "name": "ctrlKey" },
        "right": {
          "expr": "bin",
          "op": "==",
          "left": { "expr": "var", "name": "key" },
          "right": { "expr": "lit", "value": "s" }
        }
      },
      "then": [
        { "do": "set", "target": "isSaving", "value": { "expr": "lit", "value": true } }
      ]
    }
  ]
}

Next Steps

Learn how to make HTTP requests with Fetch & Effects.