Want a lightweight alternative to Vuex for managing state in Vue.js web apps?

Vue.Observable can be used as a state store instead of Vuex in simple applications. Here’s a simple way to get started:

Version 2.6.0 of Vue.js added Vue.Observable.This is a function that returns a reactive instance of a given object. In Vue, objects are not automatically reactive. That means that if we want to react when properties on an object change, we need to do some extra work. Vue.Observable makes that super easy. Read more about reactivity here.

Non-Reactivity

const obj = {x: 0, y: 0};
export default { // Vue component
    data() { return {}; },
    method() {
        updateObj() {
            obj.x = 1;
            obj.y = 2;
        }
    }
}

In this example, calling updateObj will not trigger a re-calculation of computed values nor re-render the view. Fortunately, Vue components have the data function. The object returned by data is reactive!

Reactivity via data()

export default { // Vue component
    data() {
        return {
            obj: {x: 0, y: 0}
        };
    },
    method() {
        updateObj() {
            this.obj.x = 1;
            this.obj.y = 2;
        }
    }
}

Since the result of data is made reactive, calling updateObj will cause computed values that depend on obj to be re-calculated and update the view if needed.

Breakout State from Components

In basic components/applications, all mutable data is put into the object returned by the data function. Storing all of the app’s data in data functions on each component quickly falls apart. In particular, this becomes an issue when data needs to be passed between sibling components.

Does the component with the data pass it up to the parent component via events and then the parent component passes it down to the sibling component via props? Even in simple cases, this is immediately a code smell and not a great development experience. It couples components, is difficult to test, prone to bugs, unmaintainable, and plain confusing.

This is where state stores come in.

State Management

Vuex is the go-to state store plugin for Vue.js. Here’s how vuejs.org describes Vuex:

Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.

That’s great, but Vuex is not quite trivial to use. For one, it needs to be added as a plugin to your Vue app. Two, it is very powerful, making it daunting to get started with. Finally, many applications are simple enough to not need Vuex and all its features for state management.

So, what’s the alternative to Vuex? Of course, the answer is the topic of this post: Vue.Observable.

Vue.Observable as a State Store

Finally with all the background established, here’s how to use Vue.Observable as a state store.

store.js

import Vue from 'vue';
import axios from 'axios';

const state = Vue.Observable({ // this is the magic
    radius: 0,
    color: 'red'
});

export const getters {
    radius: () => state.radius,
    color: () => state.color
}

export const mutations {
    setRadius: (val) => state.radius = val,
    setColor: (val) => state.color = val
}

export const actions {
    fetchRadiusFromApi() {
        return axios
            .get('http://localhost:5001/api/radius')
            .then((res) => {
                mutations.setRadius(res.data);
            });
    },
    fetchColorFromApi() {
        return axios
            .get('http://localhost:5001/api/color')
            .then((res) => {
                mutations.setColor(res.data);
            });
    }
}

Line 4, where we declare state, is where the important part happens. Getters and mutations are how we read and update the state. Actions are where async calls go, namely API requests. Actions commit mutations, potentially based on the results of API requests.

component.vue

<template>
    <div>
        <div>Radius: {{ radius }}</div>
        <div>Color: {{ color }}</div>
        <button @:click="setRadius(0)">Reset radius</button>
        <button @:click="fetchColorFromApi">Fetch color</button>
    </div>
</template>

<script>
    import { getters, mutations, actions } from 'store.js';

    export default {
        data() { return {}; },
        computed() {
            ...getters // radius(), color()
        },
        created() {
            this.fetchRadiusFromApi(); // fetching data right away
            this.fetchColorFromApi().then(() => {
                console.log('You can chain then after actions, if you return the request');
            });
        }
        methods() {
            ...mutations, // setRadius(val), setColor(val)
            ...actions // fetchRadiusFromApi(), fetchColorFromApi()
        }
    }
</script>

Wrapping Up

That’s it! Any components can just import store.js and share the same state. No need to use props/events to pass around data.

Bonus tip for components that don’t need all the getters or need a computed value:

component.js

computed() {
    // ...getters <- instead of this, do this:
    radius() {
        return getters.radius;
    },
    diameter() {
        return getters.radius * 2;
    }
    // this component doesn't need color
}