Chimezie Enyinnaya I'm a self-taught software developer based in Lagos, Nigeria. I enjoy teaching what I have learned and what I'm currently learning so that others can benefit from it.

State management in Alpine.js using Spruce

3 min read 1035

Alpine.js spruce state management

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">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">

      href="[email protected]/css/bulma.min.css"

    <script src="[email protected]/dist/spruce.umd.js"></script>
    <script src="[email protected]/dist/alpine.min.js"></script>
    <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>'todo', {
    todos: [],

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">
            x-for="(todo, index) in $store.todo.todos"
              <td x-text="todo.title"></td>

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">
        placeholder="What needs to be done?"

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) {

        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:


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 — .

Chimezie Enyinnaya I'm a self-taught software developer based in Lagos, Nigeria. I enjoy teaching what I have learned and what I'm currently learning so that others can benefit from it.

Leave a Reply