State Management
Managing reactive state in Orbis pages
State Management
Orbis uses a reactive state system powered by Zustand. Each page has its own isolated state store that drives UI updates.
Overview
State in Orbis flows unidirectionally:
graph LR
Definition[State Definition ] --> Store[Zustand Store ]
Store --> UI[UI Components ]
UI --> Actions[User Actions ]
Actions --> Store Defining State
State is defined in the page’s state field:
{
"pages": [
{
"id": "my-page",
"state": {
"username": {
"type": "string",
"default": "Guest"
},
"items": {
"type": "array",
"default": []
},
"settings": {
"type": "object",
"default": {
"theme": "light",
"notifications": true
}
},
"count": {
"type": "number",
"default": 0
},
"isActive": {
"type": "boolean",
"default": false
}
},
"layout": { ... }
}
]
} State Field Definition
Each state field has:
| Property | Type | Description |
|---|---|---|
type | Required | string, number, boolean, object, array |
default | Optional | Initial value (auto-generated if not provided) |
nullable | Optional | Whether the field can be null |
description | Optional | Documentation for the field |
Default Values by Type
If no default is provided:
| Type | Default Value |
|---|---|
string | "" |
number | 0 |
boolean | false |
object | {} |
array | [] |
Accessing State
State values are accessed in expressions using the state. prefix:
{
"type": "Text",
"content": "Hello, {{state.username}}!"
} Nested State Access
Use dot notation for nested objects:
{
"type": "Text",
"content": "Theme: {{state.settings.theme}}"
} Array Access
Access array elements by index or use loops:
{
"type": "Text",
"content": "First item: {{state.items[0].name}}"
} Updating State
State is updated through the update_state action:
{
"type": "Button",
"label": "Increment",
"events": {
"on_click": [
{
"type": "update_state",
"path": "count",
"value": "{{state.count + 1}}"
}
]
}
} Update Modes
The update_state action supports different modes:
Set (Default)
Replace the value completely:
{
"type": "update_state",
"path": "username",
"value": "John"
} Merge
Merge objects (shallow):
{
"type": "update_state",
"path": "settings",
"mode": "merge",
"value": {
"theme": "dark"
}
} Result: { theme: "dark", notifications: true }
Append
Add to arrays:
{
"type": "update_state",
"path": "items",
"mode": "append",
"value": { "id": 1, "name": "New Item" }
} Remove
Remove from arrays:
{
"type": "update_state",
"path": "items",
"mode": "remove",
"value": "{{$item.id === 1}}"
} Nested Updates
Update nested properties directly:
{
"type": "update_state",
"path": "settings.theme",
"value": "dark"
} Loading States
Track loading status for async operations:
{
"type": "Button",
"label": "Load Data",
"loading": "{{state.loading.fetchData}}",
"events": {
"on_click": [
{
"type": "set_loading",
"target": "fetchData",
"loading": true
},
{
"type": "call_api",
"api": "my-plugin.getData",
"on_success": [
{ "type": "update_state", "path": "data", "value": "$response.data" },
{ "type": "set_loading", "target": "fetchData", "loading": false }
],
"on_error": [
{ "type": "set_loading", "target": "fetchData", "loading": false }
]
}
]
}
} Error States
Store error messages in state:
{
"type": "call_api",
"api": "my-plugin.submit",
"on_error": [
{
"type": "update_state",
"path": "errors.submit",
"value": "$error.message"
}
]
} Display errors conditionally:
{
"type": "Alert",
"variant": "destructive",
"visible": "{{state.errors.submit}}",
"message": "{{state.errors.submit}}"
} Form Binding
Fields can bind directly to state:
{
"type": "Field",
"name": "email",
"fieldType": "email",
"bindTo": "formData.email"
} The field automatically:
- Reads its initial value from
state.formData.email - Updates
state.formData.emailon change
Two-Way Binding
{
"state": {
"formData": {
"type": "object",
"default": {
"name": "",
"email": "",
"message": ""
}
}
},
"layout": {
"type": "Form",
"id": "contact-form",
"fields": [
{ "name": "name", "fieldType": "text", "bindTo": "formData.name" },
{ "name": "email", "fieldType": "email", "bindTo": "formData.email" },
{ "name": "message", "fieldType": "textarea", "bindTo": "formData.message" }
]
}
} State Initialization
On Page Mount
Use on_mount to initialize state when a page loads:
{
"pages": [
{
"id": "dashboard",
"state": {
"data": { "type": "array", "default": [] }
},
"hooks": {
"on_mount": [
{
"type": "call_api",
"api": "my-plugin.getData",
"on_success": [
{ "type": "update_state", "path": "data", "value": "$response.data" }
]
}
]
},
"sections": { ... }
}
]
} State Cleanup
Use on_unmount to clean up when leaving a page:
{
"on_unmount": [
{
"type": "update_state",
"path": "selectedItem",
"value": null
}
]
} State Isolation
Each page has its own isolated state store:
- State doesn’t leak between pages
- Navigating away does not reset state
- Plugins can’t access other plugins’ state
Sharing Data Between Pages
For shared data, use:
- Backend storage - Persist to database
- URL parameters - Pass via navigation
- API calls - Fetch on each page load
Reactive Updates
State changes trigger automatic UI updates:
sequenceDiagram
participant User
participant Button
participant Action
participant Store
participant UI
User->>Button: Click
Button->>Action: Execute update_state
Action->>Store: setState(path, value)
Store->>UI: Notify subscribers
UI->>UI: Re-render affected components Only components using the changed state values re-render.
Performance Tips
Minimize State Size
Keep state focused on what’s needed:
// Good
{
"selectedId": { "type": "string" },
"items": { "type": "array" }
}
// ❌ Avoid - storing derived data
{
"selectedId": { "type": "string" },
"items": { "type": "array" },
"selectedItem": { "type": "object" }, // Can be derived
"itemCount": { "type": "number" } // Can be derived
} Normalize Complex Data
For complex relationships, use normalized structures:
{
"usersById": {
"type": "object",
"default": {
"1": { "name": "Alice" },
"2": { "name": "Bob" }
}
},
"userIds": {
"type": "array",
"default": ["1", "2"]
}
} Batch Updates
Multiple state changes in one action sequence are batched:
{
"events": {
"on_click": [
{ "type": "update_state", "path": "a", "value": 1 },
{ "type": "update_state", "path": "b", "value": 2 },
{ "type": "update_state", "path": "c", "value": 3 }
]
}
} This causes only one re-render, not three.
Debugging State
In development mode, state changes are logged:
RUST_LOG=debug bun run tauri dev Watch the console for state updates:
[STATE] path: "count", value: 1
[STATE] path: "settings.theme", value: "dark" Next Steps
- Expressions - Dynamic values in detail
- Event Handling - Responding to user input
- Actions - All available actions
On This Page
- Overview
- Defining State
- State Field Definition
- Default Values by Type
- Accessing State
- Nested State Access
- Array Access
- Updating State
- Update Modes
- Nested Updates
- Loading States
- Error States
- Form Binding
- Two-Way Binding
- State Initialization
- On Page Mount
- State Cleanup
- State Isolation
- Sharing Data Between Pages
- Reactive Updates
- Performance Tips
- Minimize State Size
- Normalize Complex Data
- Batch Updates
- Debugging State
- Next Steps