Example: counter (the smallest reactive component)
A record-less counter — the minimal thing that demonstrates client → server → re-render. Use this to sanity-check your install.
# app/components/counter.rb
class Counter < ApplicationComponent
include Phlex::Reactive::Streamable
include Phlex::Reactive::Component
reactive_state :count # signed state (no DB row)
action :increment
action :decrement
action :set, params: { count: :integer } # action with a typed param
def initialize(count: 0) = @count = count
def id = "counter"
def increment = @count += 1
def decrement = @count -= 1
def set(count:) = @count = count
def view_template
div(id:, class: "counter", **reactive_attrs) do
button(**on(:decrement)) { "−" }
span(class: "value") { @count }
button(**on(:increment)) { "+" }
button(**on(:set, count: 0)) { "reset" } # explicit param via on(...)
end
end
end
Render it anywhere:
render Counter.new(count: 0)
Notes
reactive_state :countsigns the count into the DOM token. On each action the server rebuildsCounter.new(count: <verified>), runs the method, and re-renders. The new count is signed into the next token automatically.on(:set, count: 0)passes an explicit param. Explicit params win over any collected form fields.- Concurrency is handled. Click
+five times fast and you get5— the client serializes requests per component and threads the fresh token through, so rapid clicks don’t clobber each other. - This is record-less on purpose. For anything backed by data, prefer
reactive_recordso state lives in the DB (see todo_list.md).