WTF is Vuex? A Beginner’s Guide To Vuex 4

Anthony Gore
Nov 30, 2016 · 11 min read
Image for post
Image for post

Vuex is an essential tool in the Vue.js ecosystem. But developers new to Vuex may be repelled by jargon terms like “state management pattern” and confused as to what they actually need it for.

Here’s the primer on Vuex that I wish I’d had when I started learning. In it, I’ll cover the high-level concepts of Vuex and show you how to use Vuex in an app.

Vuex

My trouble with understanding Vuex only began with the name.

Being an eager Vue developer I’d heard enough about Vuex to suspect that it must be an important part of the Vue ecosystem, even if I didn’t know what it actually was.

I eventually had enough of wondering, so I went to the documentation with plans of a brief skim through; just enough to get the idea.

To my chagrin I was greeted with unfamiliar terms like “state management pattern”, “global singleton” and “source of truth”. These terms may make sense to anyone already familiar with the concept, but for me they didn’t gel at all.

The one thing I did get, though, was that Vuex had something to do with Flux and Redux. I didn’t know what those were either, but I figured it may help if I investigated them first.

After a bit of research and persistence the concepts behind the jargon finally started to materialise in my mind. I was getting it. I went back to the Vuex documentation and it finally hit me…Vuex is freaking awesome!

I’m still not quite sure how to pronounce it, but Vuex has become an essential piece in my Vue.js toolbelt. I think it’s totally worth your time to check it out too, so I’ve written this primer on Vuex to give you the background that I wish I’d had.

The problem that Vuex solves

Imagine you’ve developed a multi-user chat app. The interface has a user list, private chat windows, an inbox with chat history and a notification bar to inform users of unread messages from other users they aren’t currently viewing.

Millions of users are chatting to millions of other users through your app on a daily basis. However there are complaints about an annoying problem: the notification bar will occasionally give false notifications. A user will be notified of a new unread message, but when they check to see what it is it’s just a message they’ve already seen.

What I’ve described is a real scenario that the Facebook developers had with their chat system a few years back. The process of solving this inspired their developers to create an application architecture they named “Flux”. Flux forms the basis of Vuex, Redux and other similar libraries.

Flux

The flaw is most easily understood in the abstract: when you have multiple components in an application that share data, the complexity of their interconnections will increase to a point where the state of the data is no longer predictable or understandable. Consequentially the app becomes impossible to extend or maintain.

The idea of Flux was to create a set of guiding principles that describe a scalable frontend architecture that sufficiently mitigates this flaw. Not just for a chat app, but in any complex UI app with components and shared data state.

Flux is a pattern, not a library. You can’t go to Github and download Flux. It’s a design pattern like MVC. Libraries like Vuex and Redux implement the Flux pattern the same way that other frameworks implement the MVC pattern.

In fact, Vuex doesn’t implement all of Flux, just a subset. Don’t worry about that just now though, let’s instead focus on understanding the key principles that it does observe.

Principle #1: Single Source of Truth

But any data that is to be shared between components, i.e. application data, needs to be kept in a single place, separate from the components that use it.

This single location is called the “store”. Components must read application data from this location and not keep their own copy to prevent conflict or disagreement.

// Instantiate our Vuex storeconst store = new Vuex.Store({

// "State" is the application data your components
// will subscribe to

state: {
myValue: 0
}
});
// Components access state from their computed propertiesconst MyComponent = {
template: `<div>{{ myValue }}</div>`,
computed: {
myValue () {
return store.state.myValue;
}
}
};

Principle #2: Data is Read-Only

Instead they must inform the store of their intent to change the data and the store will be responsible for making those changes via a set of defined functions called “mutations”.

Why this approach? If we centralise the data-altering logic than we don’t have to look far if there are inconsistencies in the state. We’re minimising the possibility that some random component (possibly in a third party module) has changed the data in an unexpected fashion.

const store = new Vuex.Store({ 
state: {
myValue: 0
},
mutations: {
increment (state, value) {
state.myValue += value;
}
}
});
// Need to update a value?// Wrong! Don't directly change a store value.
store.myValue += 10;
// Right! Call the appropriate mutation.
store.commit('increment', 10);

Principle #3: Mutations Are Synchronous

But this ability would be undermined if our mutations were applied asynchronously. We’d know the order our commits came in, but we would not know the order in which our components committed them.

Synchronous mutations ensure state is not dependent on the sequence and timing of unpredictable events.

Cool, so what exactly is Vuex?

Now that you have a high-level understanding of Vuex, let’s see how we’d actually create a Vuex-based application.

Setting up a Vuex to-do app

If you’d like to develop this on your local machine, the quickest way to get up and running is by creating a Vue CLI application, so let’s do that:

$ vue create vuex-example

Be sure to include Vue 3 in the Vue CLI options, but don’t include Vuex — we want to add that ourselves so we can learn how to install it!

Installing Vuex

$ cd vuex-example
$ npm i -S vuex@4
$ npm run serve

At the time of writing Vuex 4 is still in beta. To use it, you’ll have to install the beta version with the command npm i -S vuex@4.0.0-beta.4.

Creating a Vuex store

$ mkdir src/store
$ touch src/store/index.js

Let’s now open the file and import the createStore method. This method is used to define the store and its features, which we'll do in a moment. For now, we'll export the store so it can easily be added to our Vue app.

src/store/index.js

import { createStore } from "vuex";export default createStore({});

Adding the store to a Vue instance

src/main.js

import { createApp } from "vue";
import App from "@/App";
import store from "@/store";
const app = createApp(App);app.use(store);app.mount("#app");

Creating a simple app

Here’s what this app will look like when it’s complete:

Image for post
Image for post

Let’s now delete the boilerplate component file added to the Vue CLI installation:

$ rm src/components/HelloWorld.vue

TodoNew.vue

$ touch src/components/TodoNew.vue

Open that file and let’s begin with the template. Here we’ll define a form with a text input allowing the user to enter a to-do task. This input is bound to a data property task.

src/components/TodoNew.vue

<template>
<form @submit.prevent="addTodo">
<input
type="text"
placeholder="Enter a new task"
v-model="task"
/>
</form>
</template>

Moving to the component definition now, there are two local state properties — task, described above, and id which gives a new to-do item a unique identifier.

Let’s stub a method addTodo which will create the todo item. We'll be accomplishing this with Vuex shortly.

src/components/TodoNew.vue

<template>...</template>
<script>
export default {
data() {
return {
task: "",
id: 0
};
},
methods: {
addTodo: function() {
//
}
}
};
</script>

Defining store state

So let’s return to our store now and define the property state. We'll assign a function to this which returns an object. This object has one property, todos which is an empty array.

src/store/index.js

import { createStore } from "vuex";export default createStore({
state () {
return {
todos: []
}
}
});

Note: the store state is a factory function to ensure the state is fresh every time the store is invoked.

Defining mutations

So let’s add a mutations property to the store, now, and add a function property addTodo. All mutators receive the store state as their first argument. The second optional argument is the data that components calling the mutator can pass in. In this case, it will be a to-do item.

In the function body, let’s use the unshift method to add the new to-do item to the top of the todo array list.

src/store/index.js

import { createStore } from "vuex";export default createStore({
state () {
return {
todos: []
}
},
mutations: {
addTodo (state, item) {
state.todos.unshift(item);
}
}
});

Using mutations i.e. “commits”

First, let’s destructure the Vue context object to get copies of the id and task local data values.

To access the store we can use the global property this.$store. We'll now use the commit method to create a new mutation. This gets passed two arguments - firstly, the name of the mutation, and secondly, the object we want to pass, which will be a new to-do item (consisting of the id and task values).

After this, don’t forget we’ll need to iterate the id by going this.id++ and clear our input value by putting this.task = "".

src/components/TodoNew.vue

methods: {
addTodo: function() {
const { id, task } = this;
this.$store.commit("addTodo", { id, task });
this.id++;
this.task = "";
}
}

Review

  1. The user enters their todo item into the input, which is bound to the task data property
  2. When the form is submitted the addTodo method is called
  3. A to-do object is created and “committed” to the store.

src/components/TodoNew.vue

<template>
<form @submit.prevent="addTodo">
<input
type="text"
placeholder="Enter a new task"
v-model="task"
/>
</form>
</template>
<script>
export default {
data() {
return {
task: "",
id: 0
};
},
methods: {
addTodo: function() {
const { id, task } = this;
this.$store.commit("addTodo", { id, task });
this.id++;
this.task = "";
}
}
};
</script>

Reading store data

TodoList.vue

$ touch src/components/TodoList.vue

Here’s the content of the template. We’ll use a v-for to iterate through an array of to-do items, todos.

src/components/TodoList.vue

<template>
<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.description }}
</li>
</ul>
</template>

todos will be a computed property where we'll return the contents of our Vuex store. Let's stub it for now and complete it in a moment.

src/components/TodoList.vue

<script>
export default {
computed: {
todos() {
//
}
}
};
</script>

Defining getters

For example, below we have getTodos which returns the state unfiltered. In many scenarios, you may transform this content with a filter or map.

todoCount returns the length of the todo array.

Getters help in fulfilling principle #1, single-source of truth, by ensuring components are tempted to keep local copies of data.

src/store/index.js

export default createStore({
...
getters: {
getTodos (state) {
return state.todos;
},
todoCount (state) {
return state.todos.length;
}
}
})

Back in our TodoList component, let's complete the functionality by returning this.$store.getters.getTodos.

src/components/TodoList.vue

<script>
export default {
computed: {
todos() {
return this.$store.getters.getTodos;
}
}
};
</script>

App.vue

src/App.vue

<template>
<div>
<h1>To-Do List</h1>
<div>
<TodoNew />
<TodoList />
</div>
</div>
</template>
<script>
import TodoNew from "@/components/TodoNew.vue";
import TodoList from "@/components/TodoList.vue";
export default {
components: {
TodoNew,
TodoList
}
};
</script>

That’s it! We now have a working Vuex store.

Do you actually need Vuex?

But in this simple to-do app, you’d be justified in thinking Vuex is overkill. There’s no clear point where Vuex is necessary or unnecessary, but if you’re aware of the pros and cons you can probably intuit this yourself.

Pros of Vuex:

  • Easy management of global state
  • Powerful debugging of global state

Cons of Vuex:

  • An additional project dependency
  • Verbose boilerplate

As it was said by Dan Abramov, “Flux libraries are like glasses: you’ll know when you need them.”

One possible alternative in Vue 3 is to roll your own Vuex using the Composition API. It doesn’t give you the debugging capabilities of Vuex, but it’s a lightweight alternative that may work in small projects.

You can read more about this in my article Should You Use Composition API as a Replacement for Vuex?

Vue.js Developers

Helping web professionals up their skill and knowledge of…

Anthony Gore

Written by

Here to teach you Vue.js! Course creator, Blogger, Vue Community Partner, creator of “Vue.js Developers”

Vue.js Developers

Helping web professionals up their skill and knowledge of Vue.js

Anthony Gore

Written by

Here to teach you Vue.js! Course creator, Blogger, Vue Community Partner, creator of “Vue.js Developers”

Vue.js Developers

Helping web professionals up their skill and knowledge of Vue.js

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store