Anonymus
Anonymus

Reputation: 353

Why is my Stimulus JS controller firing twice?

So, I have a Rails application with webpacker, vue, turbolinks and stimulus js installed.
The problem that I am having is that even though the controller is only imported once and even if I temporarily disable turbolinks the initialize() funcition along with the connect() one get called twice.
This only happens if I do a refresh (i.e. not when I visit the page for the first time, but only if I perform a page reload).
Strangely enough the disconnect() is only getting called once (when I do leave the page)

This sucks because I need to modify the DOM in the initialize, so I get elements added twice. Does someone have any clue to what's causing this, and / or a solution?

EDIT: application.js as requested

require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")

import "stylesheets"
import "controllers"
import "components"

components/index.js

import Vue from 'vue/dist/vue.esm'
import BootstrapVue from 'bootstrap-vue'
import TurbolinksAdapter from 'vue-turbolinks'

Vue.use(BootstrapVue)
Vue.use(TurbolinksAdapter)

const components = {}
const context = require.context("components", true, /_component\.vue$/)
context.keys().forEach(filename => {
  const component_name = filename.replace(/^.*[\\\/]/, '').replace(/_component\.vue$/, '')
  const component = context(filename).default
  components[component_name] = component
})

document.addEventListener('turbolinks:load', () => {
  const app = new Vue({
    el: '#vueapp',
    mounted() {
      let input = document.querySelector('[autofocus]');
      if (input) {
        input.focus()
      }
    },
    components: { ...components }
  })
})

controllers/index.js

import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"

const application = Application.start()
const context = require.context("controllers", true, /_controller\.js$/)
application.load(definitionsFromContext(context))

in each and every file, if I console.log it gets logged only once...
only in the stimulus controller initialize or connect gets printed twice

this happens only when I reload the page, not when I first visit it.

Upvotes: 6

Views: 3360

Answers (2)

Lev Lukomskyi
Lev Lukomskyi

Reputation: 6667

You can do this to prevent calling connect twice:

export default class extends Controller {
  connect() {
    if (document.documentElement.hasAttribute('data-turbo-preview')) {
      return;
    }
    // ...this code will be called once
  }
}

This if condition prevents execution of the code for the first connect call when turbolinks/turbo restores page html from the cache.

Note: for turbolinks attribute is data-turbolinks-preview and for turbo attribute is data-turbo-preview

Upvotes: 2

Chris
Chris

Reputation: 1038

This is probably due to Turbolinks. When navigating to a new page, Turbolinks first presents a cached version of the page (first stimulus connect) and afterwards replaces the body with the actual requested version of the page (second stimulus connect). See here for more details:

https://mrcodebot.com/turbolinks-calls-stimulus-connect-twice/

Upvotes: 9

Related Questions