Lifecycle Hooks
Run actions at specific times during your form's lifecycle - like event listeners for your forms.
What Are Lifecycle Hooks?
Lifecycle hooks let you run actions at specific moments in your form's life - when it loads, when pages change, when specific fields update, and more. They're perfect for triggering API calls, tracking analytics, or reacting to user input.
The 6 Lifecycle Hooks
| Hook | When It Runs | Use For |
|---|---|---|
| onFormLoad | Once when form mounts | Load initial data, setup |
| onPageChange | Every page navigation | Track page views, save progress |
| onContextChange | When watched fields change | Auto-lookup, dependent data |
| beforeSubmit | Before form submission | Final validation |
| afterSubmit | After successful submit | Show confirmation, redirect |
| onInterval 🔒 | At regular time intervals | Token refresh, auto-save, polling |
🔒 New in v0.1.1: The onInterval hook is perfect for automatic token refresh and periodic data updates.
onFormLoad - Run Once on Mount
Runs once when the form first loads. Perfect for loading initial data.
{
"lifecycle": {
"onFormLoad": [
{
"type": "callApi",
"service": "loadUserProfile",
"then": [
{
"type": "assignContext",
"assign": {
"user.name": "{{event.name}}",
"user.email": "{{event.email}}"
}
}
]
}
]
}
}📝 What Happens:
- 1. Form mounts
- 2. API called to load user profile
- 3. Response data assigned to context
- 4. Form displays with pre-filled data
onPageChange - Run on Navigation
Runs every time the user navigates to a different page.
{
"lifecycle": {
"onPageChange": [
{
"type": "callApi",
"service": "saveProgress"
},
{
"type": "callApi",
"service": "trackPageView"
}
]
}
}onContextChange - Run When Fields Change
Runs when specific fields change. Always use the watch array!
{
"lifecycle": {
"onContextChange": [
{
"watch": ["address.zipCode"],
"actions": [
{
"type": "callApi",
"service": "lookupCity",
"then": [
{
"type": "assignContext",
"assign": {
"address.city": "{{event.city}}",
"address.state": "{{event.state}}"
}
}
]
}
]
}
]
}
}⚠️ IMPORTANT: Always Use "watch"
❌ Without watch - runs on EVERY context change (SLOW!):
{
"onContextChange": [
{
"actions": [{ "type": "callApi", "service": "expensive" }]
}
]
}✅ With watch - only runs when specific fields change (FAST!):
{
"onContextChange": [
{
"watch": ["zipCode"],
"actions": [{ "type": "callApi", "service": "expensive" }]
}
]
}Multiple Watched Fields
You can watch multiple fields and have multiple watch blocks:
{
"lifecycle": {
"onContextChange": [
{
"watch": ["address.zipCode"],
"actions": [
{ "type": "callApi", "service": "lookupCity" }
]
},
{
"watch": ["address.state"],
"actions": [
{ "type": "callApi", "service": "loadCounties" }
]
},
{
"watch": ["cart.items", "cart.discountCode"],
"actions": [
{ "type": "callApi", "service": "recalculateTotal" }
]
}
]
}
}Complete Example: Auto-Lookup Form
Let's build a form that automatically looks up city/state when you enter a ZIP code:
{
"id": "autoLookupForm",
"type": "form",
"initialPage": "address",
"context": {
"address": {
"zipCode": "",
"city": "",
"state": ""
}
},
"lifecycle": {
"onContextChange": [
{
"watch": ["address.zipCode"],
"actions": [
{
"type": "callApi",
"service": "lookupCityState",
"then": [
{
"type": "assignContext",
"assign": {
"address.city": "{{event.city}}",
"address.state": "{{event.state}}"
}
}
]
}
]
}
]
},
"services": {
"lookupCityState": {
"type": "rest",
"url": "/api/lookup/{{context:address.zipCode}}",
"method": "GET"
}
}
}🎯 The Flow:
- 1. User types ZIP code and presses Enter (or clicks away)
- 2. Context updates:
address.zipCode = "12345" - 3. Lifecycle hook detects change (watching
address.zipCode) - 4. API called automatically
- 5. Response assigned to
address.cityandaddress.state - 6. Fields auto-fill! ✨
Combining with Computed Fields
Lifecycle hooks and computed fields work great together! Use lifecycle hooks to fetch API data, then let computed fields calculate based on that data.
{
"context": {
"order": {
"subtotal": 0,
"tax": 0,
"total": 0
},
"location": {
"zipCode": "",
"taxRate": 0
}
},
"lifecycle": {
"onContextChange": [
{
"watch": ["location.zipCode"],
"actions": [
{
"type": "callApi",
"service": "getTaxRate",
"then": [
{
"type": "assignContext",
"assign": {
"location.taxRate": "{{event.rate}}"
}
}
]
}
]
}
]
},
"computedFields": {
"tax": {
"targetPath": "order.tax",
"expression": "context.order.subtotal * context.location.taxRate",
"dependencies": ["order.subtotal", "location.taxRate"]
},
"total": {
"targetPath": "order.total",
"expression": "context.order.subtotal + context.order.tax",
"dependencies": ["order.subtotal", "order.tax"]
}
}
}🔄 The Magic:
- 1. User enters ZIP code
- 2. Lifecycle hook calls API → gets tax rate
- 3. Tax rate stored in
location.taxRate - 4. Computed field detects dependency change
- 5. Tax and total recalculate automatically!
Common Patterns
Pattern 1: Load Initial Data
{
"lifecycle": {
"onFormLoad": [
{
"type": "callApi",
"service": "loadProfile",
"then": [
{
"type": "assignContext",
"assign": { "user": "{{event}}" }
}
]
}
]
}
}Pattern 2: Dependent Dropdowns
{
"onContextChange": [
{
"watch": ["country"],
"actions": [
{ "type": "callApi", "service": "loadStates" }
]
},
{
"watch": ["state"],
"actions": [
{ "type": "callApi", "service": "loadCities" }
]
}
]
}Pattern 3: Auto-Save
{
"onContextChange": [
{
"watch": ["form.firstName", "form.lastName", "form.email"],
"actions": [
{
"type": "callApi",
"service": "autoSave"
}
]
}
]
}Pattern 4: Analytics Tracking
{
"onPageChange": [
{
"type": "callApi",
"service": "trackPageView"
}
]
}Troubleshooting
❓ Hook Not Firing?
Problem: Typed in input, but lifecycle hook didn't run
Solution: Press Enter or Tab after typing! Inputs update context on blur, not every keystroke.
❓ Wrong Field Path?
Problem: Hook never triggers
// ❌ Wrong - forgot nested path
{ "watch": ["zipCode"] }
// ✅ Right - full path
{ "watch": ["address.zipCode"] }❓ Hook Runs Too Often?
Problem: API called on every single change
Solution: Add watch array to only trigger on specific fields
🔒 onInterval - Periodic Actions
Runs actions at regular time intervals. Perfect for automatic token refresh, auto-save, or polling for updates.
New in v0.1.1: Use onInterval for automatic token refresh every few minutes.
Automatic Token Refresh Every 3 Minutes
{
"lifecycle": {
"onInterval": [
{
"interval": 180000,
"actions": [
{
"type": "callApi",
"service": "refreshToken",
"then": [
{
"type": "assignSecret",
"assign": {
"accessToken": "{{event:access_token}}"
},
"encrypt": true
},
{
"type": "assignContext",
"assign": {
"tokenRefreshedAt": "{{event:timestamp}}"
}
}
]
}
]
}
]
}
}Multiple Intervals
You can have multiple intervals running simultaneously:
{
"lifecycle": {
"onInterval": [
{
"interval": 180000,
"actions": [
{ "type": "callApi", "service": "refreshToken" }
]
},
{
"interval": 30000,
"actions": [
{ "type": "callApi", "service": "checkNotifications" }
]
},
{
"interval": 60000,
"actions": [
{ "type": "callApi", "service": "autoSaveDraft" }
]
}
]
}
}Conditional Intervals
Run interval actions only when conditions are met:
{
"lifecycle": {
"onInterval": [
{
"interval": 300000,
"condition": [
{
"matchType": "exact",
"field": "isAuthenticated",
"value": true
}
],
"actions": [
{ "type": "callApi", "service": "keepSessionAlive" }
]
}
]
}
}⚠️ Important Notes
- • Intervals are automatically cleared when the form unmounts
- • Use appropriate intervals - too frequent can impact performance
- • Consider battery life on mobile devices
- • Token refresh typically every 3-5 minutes is sufficient