Skip to content

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:

ruby
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
end

Security

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:

ruby
# No parameters needed
def increment
  self.count += 1
end

# With parameters
def add_item(params)
  items << params[:item]
end

The params argument is an ActionController::Parameters object, so you can use strong parameters if needed:

ruby
def update_profile(params)
  permitted = params.permit(:name, :email, :bio)
  current_user.update(permitted)
end

Calling Actions from Views

Using the call Action

The most common way to call actions is using the live#call Stimulus action:

erb
<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:

erb
<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:

erb
<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:

ruby
def save(params)
  self.title = params[:title]
  self.description = params[:description]
  # ... save logic
end

Debounced Form Submissions

For auto-saving forms or filtering on change, use live#formDebounce:

erb
<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:

erb
<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:

erb
<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-*-param for additional parameters
html
<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 name and value to the server
html
<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)
html
<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"
html
<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)
html
<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:

  1. Priority: Any pending reactiveDebounce message is sent immediately before the form action message
  2. Order: The server applies the reactive update first, then the form action
  3. 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:

javascript
// 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:

javascript
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:

ruby
def divide(params)
  raise "Cannot divide by zero" if params[:divisor].to_i.zero?
  
  self.result = params[:dividend].to_f / params[:divisor].to_f
end

The 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

Next Steps