Dan L
Dan L

Reputation: 4439

StimulusJS with Turbolinks, have to wait for "turbolinks:load" event to execute StimulusJS controllers

I have a fairly standard Rails 5.2 app (follows pretty much all conventions) using yarn and webpacker, with stimulus version 1.1.1 in my package.json and yarn.lock file.

# package.json
{
  "name": "MY_APP_NAME",
  "private": true,
  "dependencies": {
    "@rails/webpacker": "^4.0.2",
    "coffeescript": "1.12.7",
    "stimulus": "^1.1.1"
  },
  "devDependencies": {
    "webpack-dev-server": "^3.2.1"
  }
}

From the StimulusJS Discourse page (https://discourse.stimulusjs.org/t/stimulusjs-and-turbolinks/669), starting with Stimulus 1.1, stimulus controllers execute connect/initialize methods after DOM is ready with turbolinks.

However, the only way I could get the below controller to execute properly is to add the event handler to wait until the turbolinks:load event fired.

If it's relevant info, I'm trying to use the jQuery Select2 plugin to create a custom select element.

# app/javascript/packs/controllers/intake_customization_controller.js
import { Controller } from "stimulus";

export default class extends Controller {
  static targets = [ "userIds" ]

  initialize() {
    // Code will not execute without this event handler wrapping it...
    $(document).on("turbolinks:load", ()=> {
      $(this.userIdsTarget).select2()
    })
  }
}

The HTML form:

<%= form_with model: @account, url: settings_intake_customization_path, method: :put, id: "settings-intake_customization-form", data: { controller: "intake-customization" } do |form| %>
  <%= form.collection_select :user_ids, current_account.users, :id, :name, { include_blank: false }, { multiple: true, data: { target: "intake-customization.userIds" } } %>
<% end %>

Am I missing something with setting up the stimulus controller with turbolinks?

Using the turbolinks:load event handler, I can get the functionality I want, but from what I read on the Discourse forum, I shouldn't have to use the event handler.

Is it possible that my app has "cached" an older version of Stimulus, even though the package.json says otherwise?

Upvotes: 3

Views: 4106

Answers (1)

Tyler Klose
Tyler Klose

Reputation: 153

From my understanding, Stimulus has been designed to work in conjunction with Turbolinks. I would be surprised if you needed to wrap your code inside of the event handler for turbolinks:load. It is possible that you may be looking to use the connect event rather than the initialize event because the initialize event is fired off when the controller is first instantiated. The connect event is fired off anytime the controller is connected to the DOM.

From the docs for the connection lifecycle callback, it states:

A controller is connected to the document when both of the following conditions are true:

  • its element is present in the document (i.e., a descendant of document.documentElement, the element)
  • its identifier is present in the element’s data-controller attribute

When a controller becomes connected, Stimulus calls its connect() method.

Since you want to manipulate the DOM with jQuery Select2, the connect lifecycle callback seems more appropriate to me. I will do some more research, but I would imagine that the initialize event is fired whenever the code for the controller is loaded in the browser. If that is the case, then there is a possibility the initialize event is getting fired before the part of the DOM tree that uses your controller (and the DOM element you're trying to query) has had a chance to render:

# app/javascript/packs/controllers/intake_customization_controller.js

import { Controller } from "stimulus";

export default class extends Controller {
  static targets = [ "userIds" ]

  connect() {
    $(this.userIdsTarget).select2()
  }
}

I was able to find an article says to use connect rather than turbolinks:load. This article is written by someone who has published a handful of Stimulus tutorials so he seems fairly reputable. I'm having trouble finding anything that goes into detail about the differences between initialize and connect other than that initialize is fired once and connect is fired every time it's attached to the DOM. In order for connect event to be fired again, there needs to a disconnected event fired in between

Upvotes: 4

Related Questions