Skip to content

DSL Reference

All DSL methods are available as class methods after you include ReactiveComponent in your ViewComponent.

class MyComponent < ApplicationComponent
include ReactiveComponent
subscribes_to :record
broadcasts stream: ->(record) { [record.user, :items] }
live_action :do_something, params: [:value]
client_state :expanded, default: false
end

subscribes_to(attr_name, class_name: nil, only: %i[create update destroy])

Section titled “subscribes_to(attr_name, class_name: nil, only: %i[create update destroy])”

Declares which instance variable holds the model record that drives the component. Calling this method also automatically wires the model — no changes to the model class are needed. ReactiveComponent includes ReactiveComponent::Broadcastable on the model and registers after_create_commit, after_update_commit, and after_destroy_commit callbacks that trigger broadcasts.

Parameters:

NameTypeDescription
attr_nameSymbolThe name of the instance variable (without the @ prefix).
class_name:String or nilOptional explicit model class name. Use this when the class name cannot be inferred from the attribute name (e.g. namespaced models).
only:Symbol or Array<Symbol>Limits which lifecycle events trigger a broadcast. Accepts any combination of :create, :update, :destroy. Defaults to all three.

Examples:

# Simple model — @comment ivar, resolves to Comment
class CommentComponent < ApplicationComponent
include ReactiveComponent
subscribes_to :comment
def initialize(comment:)
@comment = comment
end
end
# Namespaced model — @notification ivar, resolves to Inbox::Notification
class NotificationRowComponent < ApplicationComponent
include ReactiveComponent
subscribes_to :notification, class_name: "Inbox::Notification"
def initialize(notification:)
@notification = notification
end
end
# Only re-render on update — ignore creates and destroys
class OrderStatusComponent < ApplicationComponent
include ReactiveComponent
subscribes_to :order, only: :update
def initialize(order:)
@order = order
end
end

The framework uses this to:

  • Look up the record for data extraction and re-rendering
  • Generate DOM IDs for the component wrapper
  • Resolve the model class when handling channel updates and executing server actions
  • Auto-wire the model with after_commit callbacks (idempotent — safe to call from multiple components)

Declares the ActionCable stream that carries updates for this component. Optional — when omitted, the record itself is used as the default stream.

Parameters:

NameTypeDescription
stream:Proc or streamableThe stream identifier. Typically a lambda that receives the record and returns a streamable value (an array, string, or ActiveRecord object).
prepend_target:String or nilOptional DOM ID of a container element. When set, newly created records are rendered via Turbo Streams and prepended to this target. Requires ReactiveComponent.renderer to be configured.

Example:

class TaskComponent < ApplicationComponent
include ReactiveComponent
subscribes_to :task
broadcasts stream: ->(task) { [task.project, :tasks] },
prepend_target: "task_list"
def initialize(task:)
@task = task
end
end

The stream value is signed using Turbo::StreamsChannel.signed_stream_name before being sent to the client, preventing unauthorized subscriptions.

When prepend_target is provided, newly created records are rendered server-side and prepended to the specified DOM element via Turbo Streams. This requires ReactiveComponent.renderer to be set (e.g. ApplicationController) in your application initializer.


Registers a server-side action that can be invoked from the client.

Parameters:

NameTypeDescription
action_nameSymbolThe name of the action. A private method with this name must be defined on the component.
params:Array<Symbol>Optional list of parameter names the action accepts from the client. Only these parameters will be passed through.

Example:

class TodoComponent < ApplicationComponent
include ReactiveComponent
subscribes_to :todo
broadcasts stream: ->(todo) { [todo.list, :todos] }
live_action :toggle_complete
live_action :update_title, params: [:title]
def initialize(todo:)
@todo = todo
end
private
def toggle_complete
@todo.update!(completed: !@todo.completed?)
end
def update_title(title:)
@todo.update!(title: title)
end
end

Triggering from the template:

Actions are triggered using Stimulus data attributes on interactive elements:

<button data-action="click->reactive-renderer#action"
data-reactive-action="toggle_complete">
Done
</button>

For actions that accept parameters, pass them as data-reactive-param-* attributes:

<button data-action="click->reactive-renderer#action"
data-reactive-action="update_title"
data-reactive-param-title="New Title">
Rename
</button>

Security: Each component instance generates a signed token (live_action_token) that is embedded in the wrapper <div>. The server verifies this token before executing any action, ensuring that the component class and record cannot be tampered with.


Declares a client-only state field managed in JavaScript. Client state is useful for ephemeral UI concerns like toggles, selections, or expanded/collapsed sections that do not need to be persisted on the server.

Parameters:

NameTypeDescription
nameSymbolThe name of the state field. An instance variable with this name will be available in the template.
default:anyThe default value for the state field when the component is first rendered. Defaults to nil.

Example:

class AccordionComponent < ApplicationComponent
include ReactiveComponent
subscribes_to :section
broadcasts stream: ->(section) { [section.page, :sections] }
client_state :expanded, default: false
def initialize(section:, expanded: false)
@section = section
@expanded = expanded
end
end
<div class="accordion">
<button data-action="click->reactive-renderer#toggleState"
data-reactive-state="expanded">
<%= @section.title %>
</button>
<% if @expanded %>
<div class="accordion-body">
<%= @section.content %>
</div>
<% end %>
</div>

Client state fields are:

  • Serialized as JSON in the component wrapper’s data-reactive-renderer-state-value attribute
  • Maintained across server-driven re-renders (the client merges server data with current client state)
  • Available in the ERB template as regular instance variables
  • Initialized from the default: value on first render, or from the constructor argument if provided