Actions & Events
Actions allow you to call server-side methods from the frontend. LiveCable provides a secure, declarative API for handling user interactions.
Defining Actions
Use the actions class method to whitelist which methods can be called from the frontend:
module Live
class TodoList < LiveCable::Component
reactive :todos, -> { [] }
reactive :filter, -> { "all" }
actions :add_todo, :remove_todo, :toggle_todo, :change_filter
def add_todo(params)
todos << {
id: SecureRandom.uuid,
text: params[:text],
completed: false
}
end
def remove_todo(params)
todos.reject! { |todo| todo[:id] == params[:id] }
end
def toggle_todo(params)
todo = todos.find { |t| t[:id] == params[:id] }
todo[:completed] = !todo[:completed] if todo
end
def change_filter(params)
self.filter = params[:filter]
end
end
endSecurity
Only methods explicitly listed in actions can be called from the frontend. This prevents unauthorized access to internal component methods.
Action Parameters
Action methods can optionally accept a params argument:
# No parameters needed
def increment
self.count += 1
end
# With parameters
def add_item(params)
items << params[:item]
endThe params argument is an ActionController::Parameters object, so you can use strong parameters if needed:
def update_profile(params)
permitted = params.permit(:name, :email, :bio)
current_user.update(permitted)
endCalling Actions from Views
Using the call Action
The most common way to call actions is using the live#call Stimulus action:
<button data-action="live#call"
data-live-action-param="increment">
Increment
</button>
<button data-action="live#call"
data-live-action-param="remove_todo"
data-live-id-param="<%= todo[:id] %>">
Delete
</button>Parameters are passed using data attributes with the pattern data-live-{name}-param:
<button data-action="live#call"
data-live-action-param="add_item"
data-live-name-param="<%= product.name %>"
data-live-price-param="<%= product.price %>">
Add to Cart
</button>Form Submissions
Use live#form to serialize and submit entire forms:
<form data-action="submit->live#form:prevent"
data-live-action-param="save">
<input type="text" name="title">
<input type="text" name="description">
<button type="submit">Save</button>
</form>The action method receives all form fields as parameters:
def save(params)
self.title = params[:title]
self.description = params[:description]
# ... save logic
endDebounced Form Submissions
For auto-saving forms or filtering on change, use live#formDebounce:
<form data-action="change->live#formDebounce"
data-live-action-param="filter"
data-live-debounce-param="500">
<input type="search" name="query" placeholder="Search...">
<select name="category">
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
</select>
</form>Reactive Inputs
Use live#reactive to sync input values with reactive variables:
<input type="text"
name="search_query"
value="<%= search_query %>"
data-action="input->live#reactive">This automatically updates the search_query reactive variable and triggers a re-render.
Debounced Reactive Inputs
For search inputs or expensive operations, use live#reactiveDebounce:
<input type="text"
name="search_query"
value="<%= search_query %>"
data-action="input->live#reactiveDebounce"
data-live-debounce-param="300">Stimulus API Reference
call
Calls a specific action on the server-side component.
- Usage:
data-action="click->live#call" - Required:
data-live-action-param="action_name" - Optional: Any
data-live-*-paramfor additional parameters
<button data-action="click->live#call"
data-live-action-param="update"
data-live-id-param="123">
Update Item
</button>reactive
Updates a reactive variable with the element's current value.
- Usage:
data-action="input->live#reactive" - Behavior: Sends the input's
nameandvalueto the server
<input type="text"
name="username"
value="<%= username %>"
data-action="input->live#reactive">reactiveDebounce
Same as reactive, but debounces the update.
- Usage:
data-action="input->live#reactiveDebounce" - Optional:
data-live-debounce-param="milliseconds"(default: 200ms)
<input type="text"
name="search_query"
data-action="input->live#reactiveDebounce"
data-live-debounce-param="300">form
Serializes and submits an entire form.
- Usage:
data-action="submit->live#form:prevent" - Required:
data-live-action-param="action_name"
<form data-action="submit->live#form:prevent"
data-live-action-param="save">
<input type="text" name="title">
<button type="submit">Save</button>
</form>formDebounce
Debounces a form submission.
- Usage:
data-action="change->live#formDebounce" - Optional:
data-live-debounce-param="milliseconds"(default: 200ms)
<form data-action="change->live#formDebounce"
data-live-action-param="filter"
data-live-debounce-param="500">
<select name="category">...</select>
</form>Race Condition Handling
When a form action is triggered, LiveCable manages potential race conditions with pending reactive updates:
- Priority: Any pending
reactiveDebouncemessage is sent immediately before the form action message - Order: The server applies the reactive update first, then the form action
- Debounce Cancellation: Pending debounced form submissions are canceled
This prevents scenarios where a delayed reactive update could arrive after a form submission and overwrite changes.
Calling Actions from Custom Stimulus Controllers
Use the LiveCable blessing to call actions from your own controllers:
// Enable the blessing
import { Controller } from "@hotwired/stimulus"
import LiveCableBlessing from "live_cable_blessing"
Controller.blessings = [
...Controller.blessings,
LiveCableBlessing
]Then use liveCableAction in your controllers:
export default class extends Controller {
submit() {
this.liveCableAction('save', {
title: this.titleTarget.value,
priority: 'high'
})
}
cancel() {
this.liveCableAction('reset')
}
}The action will be dispatched as a DOM event that bubbles up to the nearest LiveCable component.
Error Handling
If an action raises an error, LiveCable displays it in the component:
def divide(params)
raise "Cannot divide by zero" if params[:divisor].to_i.zero?
self.result = params[:dividend].to_f / params[:divisor].to_f
endThe error will be rendered in place of the component with a collapsible details element showing the exception message and backtrace.
Best Practices
Do
✅ Always whitelist actions using the actions class method
✅ Use descriptive action names that indicate what they do
✅ Validate parameters inside action methods
✅ Keep action methods focused on a single task
✅ Use debouncing for search inputs and auto-save features
Don't
❌ Don't call private methods from the frontend (they won't work)
❌ Don't perform long-running operations in action methods
❌ Don't trust client input - always validate and sanitize
❌ Don't forget to handle edge cases and errors