DEV Community

Cover image for Using Vue Observable as a State Store
Austin Cooper
Austin Cooper

Posted on • Updated on • Originally published at austincooper.dev

Using Vue Observable as a State Store

Original post here: https://austincooper.dev/2019/08/09/vue-observable-state-store/

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
}

Oldest comments (2)

Collapse
 
cooperaustinj profile image
Austin Cooper

What does everyone think about using this style of state storage versus Vuex? For me, it's great when you don't need the full power of Vuex.

Collapse
 
rinzler profile image
Rinzler

Pretty straight forward, I really liked this solution and I'll definitely try it on one of my side-projects that really don't need all the complexity of the Vuex. :)