Examples
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.
Try it#
This is a real reactive component rendered right here — not a screenshot. Click the buttons; each click POSTs to /reactive/actions, the server rebuilds the component from its signed token, runs the action, and re-renders in place.
0
The component#
One include is the whole setup: include Phlex::Reactive::Component pulls in Streamable automatically. Sign the count into the DOM with reactive_state, and declare the actions a click may invoke.
app/components/counter.rb
# app/components/counter.rb
class Counter < ApplicationComponent
include Phlex::Reactive::Component # pulls in Streamable too
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(**reactive_root(class: "counter")) do
button(**on(:decrement)) { "−" }
span(class: "value") { @count }
button(**on(:increment)) { "+" }
button(**on(:set, count: 0)) { "reset" } # explicit param via on(...)
end
end
endRender 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.action :set, params: { count: :integer }declares a typed param. The schema is compiled once, at class load — so a typo'd type symbol ({ count: :interger }) raisesPhlex::Reactive::UnknownParamTypeimmediately, not silently at click time. A:integervalue that won't coerce is dropped and the keyword default (count: 0) applies, so the action never receives a fabricated value.- Actions are default-deny — only methods declared with
actionare invokable. In development and test (verbose_errors), an undeclared or typo'd action (on(:increement)) fails loudly at render time, listing the declared actions, instead of surfacing as an opaque HTTP 403 at click time. - 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_record so state lives in the DB.