Counter

The smallest live island. Here a count is kept on the server, and a button on the page adds one to that count. When the count changes, the server renders the partial again and sends it back over the wire as HTML.

Counter

count

0

doubled 0

# frozen_string_literal: true

# Generated by hibiki:rails:island — a working mini-example: one state,
# one derived, one action, one broadcasting effect.
class CounterChannel < ApplicationCable::Channel
  include Hibiki::Rails::Channel

  def build_graph
    @count = Hibiki::State.new(0)
    doubled = Hibiki::Derived.new { @count.value * 2 }

    Hibiki::Effect.new do
      broadcast_replace target: "counter_display",
                        partial: "app/recipes/counter/counter_display",
                        locals: { count: @count.value, doubled: doubled.value }
    end
  end

  # Public methods are client-invocable actions; keep helpers private.
  def increment = @count.value += 1
end

The card above shows a count and its double. Press +1 and both numbers go up, even though we never set up the hook in our code. Everything works and is made possible by the reactivity Hibiki brings.

How it fits together

The count lives in a channel. In Rails, a channel is the server side of an Action Cable connection: an object that stays alive while a page is open and answers messages from it. A Hibiki channel is one of those with a small graph of values inside it, built automatically, when the page connects, from the three reactive primitives: state, derived and effect. CounterChannel builds this graph in build_graph.

  • A state holds a value and remembers who read it. Here @count is a state that starts at 0.
  • A derived is a value worked out from other values, and worked out again when one of them changes. Here doubled is a derived whose block reads @count and returns the count times two.
  • An effect is a block of code that runs once, and runs again whenever a value it read has changed. Here the counter's effect reads @count and doubled, renders the display partial with both, and sends the HTML to the page with broadcast_replace, which swaps it in for the element whose id is counter_display.

On the rendered component, the button calls increment, one of the channel's defined actions. Every public method on a Hibiki channel is an action the page may call. When the button is pressed, the browser sends increment over the connection, and the channel runs it on the graph's own thread. The "magic" happens in this order:

  1. @count goes from 0 to 1 and tells its readers.
  2. doubled read it, so it is marked stale.
  3. The effect read both, so it runs again, renders the card's display partial with 1 and 2, and broadcasts it.
  4. Finally, Turbo receives the broadcast in this tab and replaces the card.

The island on the page

The part of a page that talks to a channel is an island. A Hibiki island is just one normal HTML element, with the channel's controls and output inside it. Here the partial _counter.html.erb is the counter's island (the island can exist anywhere on the page and doesn't have to be inside a partial).

  • hibiki_island(CounterChannel, cid:) marks the root element. It stamps the data attributes that the packaged Stimulus controller reads to subscribe to CounterChannel, and the channel builds its graph when the subscription arrives. The cid is a fresh id for this page load.
  • turbo_stream_from "counter", cid is the stream the effect broadcasts to. It is named after the channel and the cid, to prevent a card from being sent to another visitor.
  • on(:increment) on the button sends the increment action when the button is clicked. The on() helper comes with Hibiki and supports different types of events. Click is the default event type.

During the initial page load, the island renders _counter_display.html.erb once with its initial states (zeros). Then the effect's first run sends the same state from the server, replacing the initial rendering.

Generate it in your own app

The three files are what the hibiki_rails island generator writes:

bin/rails g hibiki:rails:install
bin/rails g hibiki:rails:island counter

The generator writes plain markup with minimal class attributes and none of the Tailwind class names. The island shown on this page is an example of how you can customize the island to your liking.

The counter we showed here has one state, one derived, one effect and one action: it is the smallest graph that shows all four. The next recipe (Todo List) shows a more advanced setup you can do with Hibiki.