Alpine.js is a lightweight framework for adding JavaScript behavior directly in HTML. One of its powerful features is stores, reactive state containers. This post breaks down when you should use stores, common usage scenarios and best practices.
Stores are global objects that hold reactive data accessible across components. Unlike local x-data state, stores let you share data between unrelated components.
While this can also be achieved by having a "root" component with x-data where components are nested inside of the "root" component, stores provides an improved API to achieve this.
x-data objects.Stores shine when components need to communicate. For example:
For example, we can define a global store to track "flash message" state that will be available to all components on the page:
// Store definition
Alpine.store("flash", {
isOpen: false,
toggle() {
this.isOpen = !this.isOpen;
},
});
The "flash" store can be used like so in the button component.
<!-- Button component -->
<button x-data @click="$store.flash.toggle()">Toggle Flash</button>
The flash message component can read the $store.flash contents.
<!-- Flash message component -->
<div x-data x-show="$store.flash.isOpen">...</div>
Use stores for data used across your entire app, such as:
x-data ObjectsAttaching an x-data to the root <body> can work similarly to using stores. However, it comes with a slight performance penalty as it means that Alpine will attempt to crawl all the elements inside of <body> looking for Alpine directives.
Instead, we can use stores for global data:
<!-- Before (slower for large pages) -->
<body x-data="{ flash: '' }">
<div x-show="flash">
<div x-text="flash"></div>
</div>
<!-- lots of other elements -->
</body>
<!-- After (faster) -->
<body>
<div x-data x-show="$store.app.flash">
<div x-text="$store.app.flash"></div>
</div>
<!-- lots of other elements -->
</body>
Local component state: Use x-data/$data for state isolated to a single component.
state is application based (so think context api in react, or mobx or redux or any global state management strategy) you probably want to use Store.
If the state is isolated to small component tree, (or even isolated to one page) you probably want to use component state (isolate the state locally, think useState in react)
Group related data and functionality into separate stores (e.g., $store.auth, $store.cart). Much like regular x-data attributes, functions can be defined inside of the store definition Alpine.store("myStore", { /* here */ }). These functions act like methods, since their this refers to the store.
For example, we can define an auth Alpine.js store with a user property, a login method and a logout method. login and logout have access to user via this.user.
Alpine.store("auth", {
user: null,
login() {
// do the login...
// update application state
this.user = {
/* data from login response */
};
},
logout() {
// do the logout...
// update application state
this.user = null;
},
});
We've seen how functions can be defined on the store definition object. We can use get <function> ("getter") to output fields that look like regular object properties but are computed based on other fields (derived data).
For example, given a "cart" Alpine.js store, we can compute the total price on the fly based on the items by using a get total() {} getter.
Alpine.store("cart", {
items: [],
get total() {
return this.items.reduce((sum, item) => sum + item.price, 0);
},
});
x-model$store can be combined with the x-model directive, just like a value in the component's $data:
<input x-model="$store.user.email" type="email" />
$store is more about storing application state and not storing data. State can be local to components, or shared between them. You'll use it when you have to share state.
Stores aren’t a replacement for local state, if some data is only relevant for one component, it doesn't need to be in a store.
If you find that the exact same data is required in multiple components, that's when you should think of using stores.
The Alpine.js Devtools include store inspect & edit functionality.
Stores are in-memory by default, this means store contents is not persisted across page refreshes. To persist data, you'll need to integrate the $persist plugin, see persist plugin docs
Alpine.js stores simplify managing global state. It's recommended to use them for cross-component state or app-wide settings, but unless you require these attributes, you should default to local x-data/$data values.
Key Takeaways:
$data = isolated/local state.Further Reading: