I’m in the middle of converting a Rails 6 app that didn’t use Turbolinks to Rails 7 using both Hotwire and Turbo. For context, I have a link that goes to the Rails controller’s new action, and then redirects to an edit action.
On the page that the user is redirected to, I have a div with an attached WizardController:
<div data-controller="wizard">...</div>
The WizardController.connect method depends on the baseURI of the document, as I’m testing to see if the URI string contains “new”, e.g.:
if (document.baseURI.match("/new"))
return
Before Turbo, all this worked fine, as the baseURI of the document had already changed from new to edit. After switching to Turbo, though, it’s not working. The problem is that by the time the WizardController connects, Turbo has not changed the current baseURI from new to edit. So it always finds “new” in the string, exiting the function when not intended.
I’ve gone through a few different possibilities to make this work:
Attempt #1: Using setTimeout in the WizardController.connect to run a different method with the same code that connect previously contained:
connect() {
setTimeout(() => {
this.setup()
}, 500)
}
It works, because it forces the code run after Turbo has changed the baseURI, but just feels like a hack, plus it by definition makes the page less performant.
Attempt #2. Removing the controller from the div, and attaching it after turbo:load, which allows me to use the Stimulus connect method as expected.
<div data-wizard="true" data-controller="">...</div>
document.addEventListener("turbo:load", () => {
let wizardYield = document.querySelector("[data-wizard='true']")
if (wizardYield)
wizardYield.dataset.controller = "wizard"
})
This works, but doesn’t “feel right” and makes it hard to easily understand what’s going on. I don’t like having a separate document event listener for the sole purpose of connecting this controller just to make sure Turbo’s loaded.
Attempt #3. Adding two actions to the wizard’s element and using a custom method rather than connect:
<div data-controller="wizard" action="turbo:load@document->wizard#setup load@window->wizard#setup">...</div>
This also works, and is my favorite of the three. But I’m still not wild about it, because it requires using a custom method in the Stimulus controller rather than the standard connect. And I don’t love having that separate load@window action. But without it, the setup method is only run on a Turbo visit, and is not run when the user does a manual page refresh. (I don’t understand why that’s the case - isn’t turbo:load run on a page refresh?)
Is there a ‘better’ or ‘cleaner’ way to do this? It seems like a complicated solution to something that has to have come up before. Surely others have needed to ensure that turbo:load has already run by the time Stimulus connect is being executed?