ruby-on-railsstimulusjsturbostimulus-rails

Correctly updating turbo frame source via stimulus controller


I have a form with a select tag. The select’s options are a list of exercises, and the values are the id.

When the value of that tag changes, I’m want to update a turbo frame with the response from the path /exercises/[id] using a stimulus controller.

The exercises#show action responds to both html and turbo_stream

exercises_controller.rb

respond_to do |format|
    format.html
    format.turbo_stream
end

show.html.erb

<h1>This is the html template</h1>

show.turbo_stream.erb

<%= turbo_stream.update 'exercise_details', partial: 'exercises/history' %>

exercises/_history.html.erb

<h1>This is the turbo template</h1>

history_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {

  connect() {
  }

  change(event) {
    let frame = document.getElementById('exercise_details')
    frame.src = "/exercises/" + event.currentTarget.value
    frame.reload()
  }
}

The frame is updating as expected on change, but with the html template rather than the turbo template.

How do I get the frame to update with the turbo template?


Solution

  • There are two ways you can do it. Setting a .turbo_stream url extension:

    import { Controller } from "@hotwired/stimulus"
    
    export default class extends Controller {
      change(event) {
        const frame = document.getElementById("exercise_details")
        frame.src = "/exercises/" + event.currentTarget.value + ".turbo_stream"
        // frame.reload(); // there is no need to reload
      }
    }
    

    or sending your own turbo stream fetch request, you don't need turbo frame to do this:

    import { Controller } from "@hotwired/stimulus"
    
    export default class extends Controller {
      change(event) {
        const url = "/exercises/" + event.currentTarget.value
        fetch(url, {
          headers: {
            "X-CSRF-Token": document.querySelector("[name='csrf-token']").content,
            "Accept": "text/vnd.turbo-stream.html"
          }
        })
          .then(response => response.text())
          .then(text => Turbo.renderStreamMessage(text));
      }
    }