Using `advance` turbo action to update the query string only, not the path?


I’m looking for advice on structuring turbo frames and a form so that I can filter some results, and store the state in the query string, without affecting the path of the current page.

We have a couple of routes and turbo frames for a result filtering style experience:

  • /index - a lightweight ‘shell’ that loads very quickly without any expensive fetches, SPA-style.
  • The index page contains a filters turbo frame with a form for filtering, and a results frame with a src of /results. Results are computationally expensive, hence the separate frame and route to keep the initial page load fast.

Interacting with the filter form sets the src of the results frame, to trigger fetching results.

<turbo-frame id="filters">
  <form action="/results" data-turbo-action="advance" data-turbo-frame="results" method="get">
    <!-- form contents -->

<turbo-frame id="results" src="/results">
  <!-- results -->

This is nice as the query string is updated when the user interacts with the filtering form, thanks to the data-turbo-action="advance", but the issue is that it sets the whole path to /results?x=y, as that is the target of the form, whereas I’d like to always keep the user on /index?x=y.

I’d like a solution that:

  • Always keeps the URL consistently /index, never changes it to /results
  • Keeps the query string / history in the browser in sync with the users interactions with the form
  • Triggers fetching the new results when the user interacts with the filtering form.
  • Ideally I don’t have to re-render the filters frame, as it has some UI state like opening dropdowns that should be persisted as the user interacts with the form.

The only option I can think of is not setting turbo-action on the form, and using some JS in a stimulus controller to push a navigation event and update the query string, but I’m hoping there’s a more idiomatic turbo way of achieving this?

I’m open to restructuring the turbo frames completely if there’s a better way.

I tried to programmatically write to the URL/turbo state but could not get it to work

I don’t have anything to contribute here, only to say that I’ve also been thinking about the same problem. The Turbo Drive history works really well when the page is “flat” and all navigations are related. But, the moment you have a secondary layer (ex, fly-out, modal window, complex aside), having all navigations tied to the entire URL is a point of friction.

In Angular, they have “auxiliary” routes where you can literally have multiple routable areas operating at the same time. In AngularJS (pre-auxiliary routes), I have done something similar to what you are saying, but I have to do it all manually in the JavaScript. But, instead of programmatically updating the History API, what I’ll do is when the sub-view renders, I’ll pre-compute the link-URLs in the page to build off of what is already in the address bar.

Now, in Angular, this isn’t really that complicated because all the rendering is client-side. So, it’s easy to see which URL is already loaded. However, with Hotwire, that secondary view is delivered by the server. And, the server has no sense of what URL is already rendered by the primary view. And, this is where I usually give up and stop thinking about it. :frowning:

Anyway, I just wanted to say that the struggle is real and I can related. Unfortunately, I don’t have much to add.

1 Like