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
end

Render it anywhere#

render Counter.new(count: 0)

Notes#

  • reactive_state :count signs the count into the DOM token. On each action the server rebuilds Counter.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 }) raises Phlex::Reactive::UnknownParamType immediately, not silently at click time. A :integer value 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 action are 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 get 5 — 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.