Stimulus Discourse

Connect() called multiple time - is there more information on why?

Hi,

I’ve just experience it - connect() is called multiple times on a controller. This is mentioned in the documentation as:

https://stimulusjs.org/reference/lifecycle-callbacks#reconnection

When this happens, such as after removing the controller’s element from the document and then re-attaching it, Stimulus will reuse the element’s previous controller instance, calling its connect() method multiple times.

What I could not understand is how many times. Because at the first call some of the element in the form that was replaced by rails-ujs were missing. Is there more information about this lifecycle feature?

Thanks.

Hey @thebravoman!

That seems to be written a bit confusingly - a controller’s connect() method will be called multiple times only if it becomes disconnected from the DOM, not otherwise.

From further down the page:

Lifecycle methods still run in the order they occur, so two calls to a controller’s connect() method will always be separated by one call to disconnect() .

So if you’re experiencing the connect() method being called multiple times, it’s because it’s being attached to the DOM more than once (which also means disconnect() is being called in between connect() calls).

Another common scenario can occur with Turbolinks, which will first load a cached version of the page and then render the actual page. In this case, the controller’s connect() method isn’t actually being called twice, but rather two instances of the controller are being connected. In that case, the first case is disconnected before the second instance is disconnected. You can read more on that on the Turbolinks page here.

You can easily detect if the current page is a Turbolinks cache via (taken from the Turbolinks page):

if (document.documentElement.hasAttribute("data-turbolinks-preview")) {
  // Turbolinks is displaying a preview
}

Hope this helps!

Thanks. This helps.

Here is the controller in question. It is for uploading a file.

export default class extends Controller {
  static targets = [ 
    "parent",
    "uploadedFiles",
    "container",
    "uploadDataHidden",
    "uploadDataFile"
   ]

  connect() {
    if(!this.hasUploadDataFileTarget) {
      return
    }
   ...
  }

The issue is that sometimes this…hasUploadDataFileTarget returns false for the same form. What I still can not understand is why would the form sometimes contain the target and sometimes not contain the target. Even if it is called multiple times the form as a whole always contains the target input, i think.

Turbolink is enabled.

That’s odd. What type of tag is uploadDataFileTarget? An input tag with type="file"?

Yes.

<%= f.file_field :source, 
                data: {
                    target: "upload-controller.uploadDataFile"
                }, 
                placeholder: t("helpers.placeholder.organization.picture") %>

Have you tried to ignore the Turbolinks preview?

connect() {
    if(!this.hasUploadDataFileTarget || document.documentElement.hasAttribute("data-turbolinks-preview")) {
      return
    }

@thebravoman That’s really odd. I created a similar setup and tested it out and the file input is always present as a target during connect() for a simple form, whether it’s a preview or not. I suspect there’s something more going on in your specific situation, but it’s hard to guess what that might be.

I would be curious to know if when the file input target is not present, it is always when it’s a preview or if there’s some other factor that affects that.

I will try, but I don’t want to couple stimulus with turbolinks.