hotwired / stimulus

A modest JavaScript framework for the HTML you already have

Home Page:https://stimulus.hotwired.dev/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Question about Stimulus' life cycle

fritzmg opened this issue · comments

I have a question or may be a misconception about the life cycle of Stimulus controllers.

Let's say I have a simple controller that toggles a <body> class and stores that state in localStorage:

// foobar_controller.js
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
    toggle() {
        document.body.classList.toggle('foobar');
        localStorage.setItem('foobar', document.body.classList.contains('foobar'));
    }
}
<head>
    <script src="app.js" async></script>
</head>
<body><button type="button" data-controller="foobar" data-action="foobar#toggle">Toggle Class</button></body>

Now I also want to restore that body class state which is saved in localStorage. My initial though was to put that into the initialize() method:

import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
    initialize() {
        if ('true' === localStorage.getItem('foobar')) {
            document.body.classList.add('foobar');
        }
    }

    
}

My expectation was that (once app.js is loaded and cached by the browser) this would be immediately executed as soon as the node the controller is registered on is started to be processed by the browser.

However, in actuality it kind of looks like all Stimulus controllers are only initialized once the whole page finished loading - i.e. it almost looks like everything is only processed on DOMContentLoaded. I assumed that stimulus controllers would be initialized as soon as possible via Mutation observers. Thus in this case the body class is added too late for my use case.

Or another example would be the click action for a menu button - the event listener only seems to be added once the whole DOM was loaded, meaning that on a large page the menu button (which is at the very top of the page typically) does nothing for quite some time.

Was my expectation wrong? Or is there something wrong with my setup. In my case I use webpack - together with hotwired/stimulus-webpack-helpers. The app.js looks like this for example:

import { Application } from '@hotwired/stimulus'
import { definitionsFromContext } from '@hotwired/stimulus-webpack-helpers'
import './css/app.scss';

// Start Stimulus application
const application = Application.start()
const context = require.context('./controllers', true, /\.js$/)
application.load(definitionsFromContext(context));
application.debug = process.env.NODE_ENV === 'development';

However, in actuality it kind of looks like all Stimulus controllers are only initialized once the whole page finished loading - i.e. it almost looks like everything is only processed on DOMContentLoaded

you are right

please check this PR https://github.com/hotwired/stimulus/pull/131/files#diff-f3d6212b4c84ebef921d7f88435f66b4637ec31917aa74d48849e15773d6670cR25

so Application#start is async function that awaits for DOMContentLoaded runs all observers only after

I see, thank you for clarifying.

Ideally though it would be great if Stimulus controllers and actions are initialised as soon as possible - otherwise it might take a long time until event listeners are registered for example, e.g. when the DOM is large (see menu button example above).

However, I realise this might be tricky to implement, especially if controllers depend on each other.