Alpine.js v3 Global Store initialization timing with x-init
0 reputation · 23 Oct 2020, 17:41 UTC
0 reputation · 23 Oct 2020, 17:41 UTC
Alpine.js v3 utilizes JavaScript Proxies for its reactivity system, enabling the Alpine.store() feature to maintain state across decoupled components. When implementing global stores, the initialization sequence determines when component-level logic can reliably access this shared state.
The x-init directive is designed to execute logic as a component enters the DOM. However, there is a potential timing gap between the registration of a global store and the execution of x-init in components that depend on that store's initial values for their own setup logic.
In complex applications where stores are defined dynamically or loaded via external modules, the order of operations can affect whether the initial render reflects the store's default state or a modified state triggered by x-init.
x-init guarantee execution after Alpine.store() definitions are fully processed?34775 reputation · 23 Oct 2020, 18:58 UTC
In Alpine.js v3, x-init does not provide a universal guarantee of execution after all Alpine.store() definitions are processed. The timing depends entirely on when the store is registered relative to the Alpine.start() call.
Alpine.store() is called in a script block that executes before Alpine.start(), the store is guaranteed to be available when x-init runs.Alpine.start() (e.g., via a dynamic import() or an external module loaded late), x-init may execute before the store exists, resulting in undefined values or errors during the initial component setup.To ensure components do not render with stale or missing data, use one of the following strategies based on your loading architecture:
The most reliable method is to define all global stores in a separate configuration file or script block that is executed before the Alpine core initializes.
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('auth', { user: null, initialized: false });
});
</script>
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
If stores must be loaded dynamically, include a ready or initialized boolean in the store. Use x-cloak combined with an x-show or x-if directive to hide the component until the store confirms it is populated.
<div x-data x-show="$store.auth.initialized" x-cloak>
</div>
You can verify the initialization order in your specific environment using the following scoped test:
console.log('Store Defined') inside your Alpine.store() block.x-init="console.log('Component Init')" to your target element.Diagnostic Detail Needed: Are your stores being defined within the alpine:init event listener, or are they being registered inside asynchronous fetch calls or dynamic module imports?
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 24 Oct 2020, 03:17 UTC
The critical detail is that Alpine.store() registers synchronously, but x-init only runs once during a component's initialization phase. If you call Alpine.store() after Alpine.start() (e.g., in a dynamic import), any components already in the DOM have already executed their x-init and will never see the store.
The reliable pattern is to register a placeholder store before Alpine.start() with a readiness flag:
Alpine.store('app', { loading: true, data: null, ready: false })Then mutate loading, data, and ready when your async payload arrives. Components should read the store via x-effect or template bindings (x-text="$store.app.data") rather than capturing values in x-init. This makes the initial render show the loading state and react automatically when ready flips, eliminating the timing gap entirely.