Solving Dynamic Updates in Rails with Turbo Stream and Stimulus
Recently, I ran into a situation in a Rails project where I needed to update part of a page dynamically when a user selected a new shipping address. The requirement was simple on paper:
When the user changes the address, instantly update the shipping cost in the order summary — without reloading the page.
That’s exactly the kind of thing Turbo Stream is great for… but it didn’t work at first.
The Problem (Before)
My initial setup relied on a simple form submission and Turbo Streams’ automatic magic. I had a select
dropdown for addresses and expected Turbo to pick up the POST request and render the new shipping cost.
But here’s what happened:
- The page wasn’t updating automatically.
- Turbo Stream requests weren’t being triggered the way I expected.
- I needed to send multiple pieces of data (address ID and multiple order IDs) together, which was tricky with just a standard form.
It became clear I needed more control over the request — both the data being sent and how the Turbo Stream response was rendered.
The Fix (After)
I rewrote the interaction using a Stimulus controller to:
- Capture the dropdown change event.
- Gather all the required data.
- Send a
POST
request viafetch
with the correct headers. - Render the Turbo Stream response manually.
Here’s the final code:
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
select(event) {
const addressId = event.target.value
const orderIds = event.target.dataset.shippingAddressOrderIds.split(',')
const token = document.querySelector('meta[name="csrf-token"]').content
fetch("/orders/shipping_cost", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": token,
"Accept": "text/vnd.turbo-stream.html"
},
body: JSON.stringify({
address_id: addressId,
order_ids: orderIds
})
})
.then(response => response.text())
.then(turboStreamHTML => {
Turbo.renderStreamMessage(turboStreamHTML)
})
}
}
How It Works
-
Listening for Changes The
select
method runs whenever the address dropdown changes. -
Gathering Data
addressId
: From the selected value.orderIds
: Pulled from adata-shipping-address-order-ids
attribute.
-
Making the Turbo Stream Request We send JSON to the server with the right CSRF token and an
Accept
header requesting a Turbo Stream response. -
Applying the Update We manually call
Turbo.renderStreamMessage()
to immediately apply the Turbo Stream changes without a page reload.
Why This Works Better
This approach gives:
- Control over the request and response cycle.
- Flexibility to send exactly the data the server needs.
- Instant UI updates without relying on form submissions or page reloads.
Turbo and Stimulus together are powerful, but sometimes you need to step in and handle the request manually — and that’s exactly what solved this problem.