If you’ve used JavaScript frameworks such as React and Vue.js, then you may already be familiar with the concept of state management, which creates a way to communicate and share data across components.
Ideally, users require state management when their application has two or more components that should communicate and share data without having to declare the data in the scope of each component and manually pass them across. Typically, users have some kind of store, which serves as a single source of truth for an application state/data.
Enter Alpine.js, a relatively new JavaScript framework that borrows concepts from React and Vue.js and also has its own implementation of state management through a library called Spruce. Spruce is a lightweight state management library for Alpine.js, and, just like Alpine.js, Spruce is simple and has a small footprint.
What we’re building
In this article, we’ll build a simple to-do application that comprises two components: an input for adding new to-dos and a table displaying the list of to-dos. This will give us the opportunity to access state from a global store inside the two independent components.
Getting started with Spruce
To get started, let’s create a new project directory, which we’ll call alpine-spruce-todo
:
mkdir alpine-spruce-todo
Next, create an index.html
file inside the project directory.
cd alpine-spruce-todo touch index.html
Just like Alpine.js, Spruce can be installed either from a CDN or using npm or Yarn. In this tutorial, we’ll use a CDN. Add the following code inside index.html
:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Todo</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css" /> <script src="https://cdn.jsdelivr.net/npm/@ryangjchandler/[email protected]/dist/spruce.umd.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/alpine.min.js"></script> </head> <body> <section class="section"> <div class="container"> <div class="columns"> <div class="column is-three-fifths is-offset-one-fifth">
Because we are using a CDN, we need to pull in Spruce before Alpine.js. For styling, let’s use Bulma CSS, which we’ll also pull in from a CDN.
Creating a global store
To start using Spruce, we need to define a global store, which will serve as a single source of truth for all of our application’s components. Let’s create our application’s global store.
Add the following snippet just before the closing body tag:
<script> Spruce.store('todo', { todos: [], }) </script>
We are making use of the CDN build, which means Spruce is available on the window scope. Using the Spruce
variable, we call the store
method to create the store.
The method takes two arguments: the name of the store and the state (data) of the store. Because we are building a todo application, it makes sense to name the store todo
. The store only has one state (todos
), which is an array of todos, which we’ll set to be empty by default.
Accessing the state
With a global store in place, we now need to determine how to access the store from our components. Luckily for us, Spruce makes this seamless by exposing a $store
magic property.
Replace the “component goes here” text with the following code:
<div x-data="{}"> <template x-if="$store.todo.todos.length"> <div class="box mt-5"> <table class="table is-fullwidth"> <tbody> <template x-for="(todo, index) in $store.todo.todos" :key="index" > <tr> <td x-text="todo.title"></td> </tr> </template> </tbody> </table> </div> </template> </div>
This is a typical Alpine.js component, but you’ll notice that we didn’t declare any scope for the component. That’s because we want to make use of the data from our global store so that we can access the todo
store using $store.todo
and, subsequently, the store properties.
First, we’ll check to make sure the todos
array contains some todos. Then we’ll display the todos in a table by iterating through the todos
array.
Modifying store state
We have seen how to access state in a store. What if we want to modify the state? For that, we follow similar steps to what we’ve already done. To modify a state, we’ll simply reassign a new value to the state. But because we are working with an array as our state, we’ll have to push new items to the array.
Add the following code before the previous component:
<div x-data="todoInput()"> <div class="field"> <div class="control"> <input type="text" class="input" x-model="newTodo" placeholder="What needs to be done?" @keyup.enter="addTodo" /> </div> </div> </div>
This is a typical Alpine.js component, but this time, the component has some scope of its own, which we’ll extract into a separate function called todoInput()
. The input is bound to a newTodo
data, and once the enter key is pressed we call the addTodo
method.
Let’s create the function. Add the snippet below after the code for defining the Spruce store:
function todoInput() { return { newTodo: '', addTodo() { if (!this.newTodo) { return } this.$store.todo.todos.push({ title: this.newTodo, }) this.newTodo = '' } } }
Because we are accessing the store from a function, we need to make use of this
. We simply add the new todo to the todos
array.
More great articles from LogRocket:
- Don't miss a moment with The Replay, a curated newsletter from LogRocket
- Learn how LogRocket's Galileo cuts through the noise to proactively resolve issues in your app
- Use React's useEffect to optimize your application's performance
- Switch between multiple versions of Node
- Discover how to animate your React app with AnimXYZ
- Explore Tauri, a new framework for building binaries
- Advisory boards aren’t just for executives. Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Conclusion
In this tutorial, we covered the definition of a state management, and why and when to use it. We’ve also learned how to apply state management in Alpine.js using Spruce, and we saw how to access and modify a store’s state.
To learn more about Spruce, check out the GitHub repo.
Are you adding new JS libraries to improve performance or build new features? What if they’re doing the opposite?
There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.
LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.

LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Build confidently — Start monitoring for free.