Turbo Stream in Hotwire
Turbo provides three functionalities: Drive, Frame and Stream. Turbo drive is quite similar to Turbolinks, which replace the body part of html page while keeping head part untouched to reduce time in loading assets (stylesheets & javascript). Turbo frame advances this idea to load only the specified element in HTML. Therefore, only a portion of web page is updated while the rest stay the same. But how about the Turbo stream, which is often mentioned along with ActionCable and WebSocket. It seems a way to update index (list) of models, but why does it use ActionCable ?
In some senses, you can consider Turbo Stream a replacement of Rails Unobtrusive Javascript! Look at the destroy method:
# messages_controller.rb
def destroy
@message = Message.find(params[:id])
@message.destroy
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.remove(@message) }
format.html { redirect_to messages_url }
format.js
end
end# destroy.js.erb
document.getElementById(<%= dom_id(@message) %>).remove();
For Turbo Stream and Unobtrusive Javascript, They both remove the element with id to @message. Then why should we need Turbo Stream ? The main issue is HTML templating. For all front-end (client-side) frameworks, there are HTML template syntax because it is not easy to contruct HTML from pure javascript. Turbo Stream allows you to construct HTML with all the facilities from Rails and transfer the final fragment to the front end. While working with Javascript provides more flexibility, they are not very useful most of time. Turbo Stream allows you to append, prepend, replace and delete any given HTML element by its id. If you use Vue and React in the front end, you often need to transfer the data in JSON format from server to client, either by embed JSON data in HTML or get that data in a separated web request, and format the JSON data with Vue or React template syntax, not to mention that localization and date time handling are redudant from Rails. With Turbo Stream, everything happens in server side.
The ActionCable part is just a bonus that an update from Rails server can be applied to multiple clients at the same time through WebSocket. Therefore, all your clients receive the same update.
In short, Turbo Stream is more about constructing and updating HTML fragment than WebSocket. Keep that in mind, it is not limited by updating index (list) of models. You can use it to update any HTML element with proper DOM id.
For example, to update message:
# PATCH/PUT /messages/1
def update
respond_to do |format|
if @message.update(message_params)
format.turbo_stream {
dom_id = helpers.dom_id(@message)
s = turbo_stream.replace dom_id, "<div>replaced</div>"
render turbo_stream: s
}
format.html { redirect_to @message, notice: 'Message was successfully updated.' }
end
end
end
As you can see in update
method, you can synthesize the HTML fragment with turbo_stream.replace
with proper DOM id and inner HTML, then update that part with render turbo_stream
. Because turbo_stream.replace
returns as string, you can also use partial like this:
turbo_stream.replace(dom_id, partial: "messages/message",
locals: { message: message })
And if you use view_component, you can still get the final HTML fragment like this:
s = turbo_stream.replace dom_id do
view_context.render(MessageComponent.new(message: @message)
end
render turbo_stream: s
That’s it for Turbo Stream !