2022-02-15

Turbo stream responses with request.js

I’ve been tinkering with request.js and it’s quite magical. From the client, we can make make typical REST requests get, post, put, patch, destroy. We can even deliver a turbo-stream response. By adding responseKind: "turbo-stream" to the options config, you can receive some_action.turbo_stream.erb. When hooked with a stimulus controller, the implementation is easier than you would imagine.

<!-- pages/index.html.erb -->
<div class="flex flex-col" data-controller="activity">
  <div>
    What would you like to do?
    <%= select_tag "activity",
                   options_for_select(@options),
                   { include_blank: true,
                     data: { activity_target: "input",
                             url: what_to_do_path,
                             action: "change->activity#select" } } %>
  </div>
  <div>
    <div data-activity-target="loading" class="hidden">Loading...</div>
    <%= turbo_frame_tag "option" %>
  </div>
</div>

In the stimulus controller I import get from the request.js library. When a change event is triggered on the select tag, select() makes a get request. Notice how I use new URLSearchParams to append the param to the url. You can also return a Promise object with your request.

// javascript/controllers/activity_controller.js
import { Controller } from "@hotwired/stimulus"
import { get } from "@rails/request.js"

export default class extends Controller {
  static targets = [ "loading", "input" ]

  select(event) {
    let url = this.inputTarget.dataset.url
    let body = document.getElementById("option")
    let params = new URLSearchParams()
    params.append("option", this.inputTarget.value)


    this.loadingTarget.classList.remove("hidden")
    body.textContent = ""

    const request = get(`${url}?${params}`, { responseKind: "turbo-stream" })
    request.then((response) => {
      this.loadingTarget.classList.add("hidden")
    })
  }
}

From the controller, in your respond_to specify the turbo_stream format.

# controllers/pages_controller.rb
def what_to_do
  @result = params[:option]
  sleep 0.5
  respond_to do |format|
    format.turbo_stream
  end
end

And finally, deliver HTML over the wire with your turbo_stream ☺︎

# views/pages/what_to_do.turbo_stream.erb
<%= turbo_stream.replace "option" do %>
  <%= turbo_frame_tag "option" do %>
    <% unless @result.blank? %>
      Then you should <%= @result.downcase %>!
    <% end %>
  <% end %>
<% end %>