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: