Conceptually understanding how to apply a turbo stream

I’m currently playing with Turbo and Stimulus with a node backend, so I can try and understand at a conceptual level how it works.

One thing I haven’t figured out is how to manage a turbo stream. Let’s say I want to implement an infinite scroller list view. The initial load will be handled by the server, and it will include the first 10 records.

I want to perform an action to load the next 10, and append them to the list. For simplicity’s sake, let’s just say there is a “next 10” button at the bottom of the list, so that when I click on it, the client will make a request to a different url (say /items/page/2). The server can just return the <turbo-stream> snippet of html, with the next 10, and append them to the list.

I can do the above approach and it works as expected. However, if I were to visit that page directly, the server would only return that <turbo-stream>, and I do not see the whole page rendered.

So I presume the server needs to know when the client wants the turbo stream version of a page vs. the whole page. I see that there is a turbo-stream=* content type parameter, but that seems to always be present when using the Turbo.visit API, which seems to be the mechanism that handles navigating with the back/forward buttons, so that won’t work either.

I feel like I’m missing some high level pieces here. Some help would be highly appreciated!

have you tried using a turbo-frame with lazy loading, responding as a turbo stream ?

Wouldn’t that require an additional request for the 1st page load, for just the frame content? From what I read in the docs, it sounded like you could have the first page server response include the content, and only need to do subsequent server calls for subsequent pages?

well, yeah, it would require an additional request, maybe you could render the partial directly in the first load

<div id='items'>
  <%= render @items %>
</div>

That is what I am doing now. The problem is that the backend cannot determine what is a ‘first load’. If it’s a direct visit to the page, it’s not handled by Turbo, so the “turbo-stream” is not in the Accept HTTP header. But if I navigate with the back/forward browser buttons, when I return to the first load page, the “turbo-stream” is sent, so the backend doesn’t think it’s a first load.

If I understood correctly, you should split your implementation into index and search endpoints, wheres the index page should work with either html or turbo_stream mime type, and the search returns the items. I think it makes more sense when it comes to components and partial, even though you have an additional request, current SPA have the same approach…
I implemented a search form this days, and it did not cross my mind that the secondary requests would be a problem

Let me see if I understand right:

So the /index route on the server would always just render the whole page. Then something on the client would trigger the /search server route, which just returns the <turbo-stream> snippet?

Maybe what I’m missing is how to properly trigger calling out to that secondary route to get the next set of records.

well, you just need to set the src attribute, turbo will execute the secondary request for you.
https://turbo.hotwire.dev/handbook/frames#cache-benefits-to-lazily-loading-frames

<turbo-frame id= 'posts' src ='/search'>
  loading
</turbo-frame>

But that puts us back in the two-request-on-first-load scenario. Perhaps I am misreading the documentation and thinking that you could avoid a second request on the first page load. I’ll see if I can poke around the internals of the ruby integration to see what html is being generated to do this.

1 Like