Managing State in Vue.js

By Hassan Djirdeh (@djirdehh)

Hassan Djirdeh
Jun 26, 2018 · 11 min read

I’ve recently given a few talks on the multiple different ways one can manage application level state in a Vue.js application. Realizing a lot of the content I’ve shared might be useful if in a written format, I’ve prepared this article to summarize the majority of ways we can think about managing state (i.e. data) in a Vue application.

State Management

Vue components are the building blocks of Vue apps by allowing us to couple markup (HTML), logic (JS), and styles (CSS) within them.

Here’s an example of a Single-File component that displays a series of numbers from a data property:

Each Vue component contains a function that prepares the component to be reactive. If a property value that’s being used in the template changes, the component view will re-render to show the change.

In the example above, is the array stored within the function. What if was a data value that needed to be accessed from another component? For example, we may need a component to be responsible in displaying (like above) and another to manipulate the value of .

If we want to share between multiple components, doesn’t only become component level data but also application level data. This brings us to the topic of State Management - the management of application level data.

Before we address how we can manage state in an application, we’ll begin by looking at how props and custom events can share data between parent and child components.

Props and Custom Events

Assume we have a hypothetical application, that at first only contains a parent component and a child component. Like other front-end frameworks that exist, Vue gives us the ability to use props to pass data from the parent down to the child.

Using props is fairly simple. All we essentially need to do is bind a value to the prop attribute where the child component is being rendered. Here’s an example of using props to pass an array of values down with the help of the v-bind directive:

The passes the array as props of the same name down to . simply binds the value of on to its template with the help of the Mustache syntax.

Here’s a CodeSandbox example of the above:

Note: All running code examples in this article are provided within a webpack bundled project based on the template. The code that we’ll be writing will only fit within the folder of the projects.

#1: props can be used to pass data from parent components down to child components.


What if we needed to find a way to communicate information in the opposite direction? An example of this could be allowing the user to introduce a new number to the array presented in the example above from the child component.

We can’t use since can only be used to pass data in a uni-directional format (from parent down to child down to grandchild…). To facilitate having the child component notify the parent about something, we can use Vue custom events.

Custom events in Vue behave very similar to native JavaScript custom events but with one key distinction - Vue custom events are used primarily for communication between components as opposed to communication between DOM nodes.

Here’s an example of using custom events to have a be able to facilitate a change to a's data property:

The has an input that captures a value and a button that emits a custom event with the captured value.

On the , a custom event listener denoted by , is specified where the child component is being rendered. When this event is emitted in the child, it pushes the value from the event to 's array.

Here’s a live example of this:

#2: Custom events can be used to create communication from child to parent components.


We can use props to pass data downwards and custom events to send messages upwards. How would we be able to either pass data or facilitate communication between two different sibling components?

We can’t use custom events the way we have above because those events are emitted within the interface of a particular component, and as a result the custom event listener needs to be declared on where the component is being rendered. In two isolated components, one component isn’t being rendered within the other.

There are roughly 3 main ways we can begin to manage information between sibling components and as a result start to handle application wide state management:

  1. Use a global EventBus
  2. Use a simple global store
  3. Use the flux-like library Vuex

EventBus

An EventBus is a Vue instance that is used to enable isolated components to subscribe and publish custom events between one another.

Wait… didn’t we just say isolated components can’t trigger and listen to custom events between one another? They normally can’t, but an EventBus helps us achieve this by being made global for this purpose.

Here’s an example of creating an EventBus instance within an file:

We can now use the interface of the EventBus to emit events. Let’s assume we have a component that’s responsible in sending a custom event when a button is clicked. This custom event, , will pass a value of what the user types in an input:

Now we can have a completely isolated component, , that’ll display a list of number values and listen to if a new number is being entered in :

We’re attaching the EventBus listener, , on the lifecycle hook of the component. When emits the event, it will pass a value within the event object. listens and pushes that new to its data array.

Here’s a CodeSandbox of the example:

This answers the question we had in mind - An EventBus can be used to facilitate communication between sibling components:

Notice how easy it was to set up and use an EventBus? Unfortunately, an EventBus also brings along a clear disadvantage. Imagine our hypothetical application looked more like this:

Assume all the white lines are props that’s being passed from the parent down to all the children, and the yellow dashed lines are events being emitted and listened from and to a component. Each of these events aren’t being tracked and can be fired from anywhere in our application. This makes things hard to maintain really quickly which can make code frustrating to work with and can become a source of bugs.

This is the one of the main reasons as to why the Vue style-guide states that an EventBus is not the recommended approach to application wide data management.

#3: An EventBus is an easy way to start having all components communicate with one another but doesn’t always scale well for medium to large applications.


Simple Global Store

Let’s look to another way we can handle application data.

Simple state management can be performed by creating a store pattern that involves sharing a data store between components. The store can manage the state of our application as well as the methods that are responsible in changing the state.

For example, we can have a simple store like the following:

The store contains a array within its state, and an method that accepts a payload and directly updates the value.

We can have one component that’s responsible in displaying the array from the store that we’ll call :

We can now have another component, called , that will allow the user to add a new number to our data array:

The component has an method that calls the mutation and passes the expected payload.

The store method receives the payload and directly mutates the array. Thanks to Vue’s reactivity, whenever the array in store state gets changed, the relevant DOM that depends on this value ( of ) automatically updates.

When we say components interact with one another here, we’re using the term ‘interact’ loosely. The components aren’t going to do anything to each other but instead invoke changes to one another through the store.

Here’s a CodeSandbox of the example we’ve laid out above:

If we take a closer look at all the pieces that directly interact with the store, we can establish a pattern:

  • The method in has the responsibility to directly act on the store method, so we can label it as a store action.
  • The store method has a certain responsibility as well - to directly mutate the store state. So we’ll say it’s a store mutation.
  • doesn’t really care about what type of methods exist in the store or in , and is only concerned with getting information from the store. So we’ll say Component A is a store getter of sorts.

An action commits to a mutation. The mutation mutates state which then affects the view/components. View/components retrieve store data with getters. We’re starting to get very close to the Flux-like architecture of managing state.

#4: A simple store can be a more manageable way to manage application state by allowing components to depend on an external store.


Vuex

Vuex is a Flux-like, state management library built solely for use with Vue.

For those who are unfamiliar - Flux is a design pattern created by Facebook. The Flux pattern is composed of 4 parts organized as a one-way data pipeline:

Vuex was inspired largely by Flux but also takes inspiration from the Elm Architecture. The heart of a Vuex integration is the Vuex Store:

The Vuex Store is made complete with 4 objects - state, mutations, actions, and getters.

State is simply an object that contains the properties that need to be shared within the application:

This state object only has a array.

Mutations are functions responsible in directly mutating store state. In Vuex, mutations always have access to state as the first argument. In addition, Actions may or may not pass in a payload as the second argument:

In Flux architectures, mutation functions are often characterized in capital letters to distinguish them from other functions and for tooling/linting purposes. Above we have an mutation that expects a payload and pushes that payload to the array.

Actions exist to call mutations. Actions are also responsible in performing any or all asynchronous calls prior to committing to mutations. Actions have access to a object that provides access to state (with ), to getters (with ), and to the commit function (with ).

Here’s an example of an action that simply directly commits to a mutation while passing in the expected payload:

Getters are to a Vuex store what computed properties are to a Vue component. Getters are primarily used to perform some calculation/manipulation to store state before having that information accessible to components.

Like mutations, getters have access to state as the first argument. Here’s a getter called that simply returns the array:

For such a simple implementation like this, a Vuex store may not really be necessary. The examples above were provided to show the direct difference in implementation between using Vuex and a simple global store.

When a Vuex store is prepared, it’s made available to a Vue application by declaring the store object within the Vue instance.

With a well built Vuex store, components often do one of two things. They either:

  1. GET state information (by accessing store state or getters) or
  2. DISPATCH actions.

Here’s a component that directly displays the array by mapping the store getter on to the components computed property.

A component can hold the responsibility to allow the user to add numbers to by mapping an component method to the store action of the same name:

Here’s the above Vuex example in CodeSandbox:

We can see that Vuex extends the simple store method by introducing explicitly defined actions, mutations, and getters. This is where the initial boilerplate as well as the main advantage comes in to using Vuex. In addition, Vuex integrates with the vue-devtools to provide time-travel debugging.

Here’s a quick gif on how the vue-devtools helps us observe store information as mutations occur, as well as being able to time-travel the UI to the moment a particular mutation has occured.

Note: The app in the gif above is the TodoMVC example implementation.

Vuex isn’t the only Flux-like library that can be used with Vue. For example, community supported libraries like redux-vue or vuejs-redux exist to help bind Vue components with a Redux store. However, since Vuex was tailored to be used only for Vue applications - it’s definitely the easiest to integrate with on to a Vue application.

#5: Vuex extends a simple store method by introducing robust steps to having our application manage state.

What’s the correct way?

Often times you may find people try to understand what the best approach is. I don’t necessarily believe there is a correct or wrong approach since each of these methods come with their advantages and disadvantages.

EventBus

- Pro: Incredibly easy to set-up.
- Con: Unable to properly track changes as they occur.

Simple Store

- Pro: Relatively easy to establish.
- Con: State and possible state changes aren’t explicitly defined.

Vuex

- Pro: The most robust way to manage application level state and integrates with Vue dev-tools.
- Con: Additional boilerplate and requires Flux-like understanding.

At the end of the day, it’s up to us to understand what’s needed in our application and what the best approach may be.

I hope this was helpful and/or made you learn something new!

All the code examples provided here can be found on Github at the repo:

If you want to watch a slightly more detailed talk on this topic - here’s a video as well!

And finally, if you’ve never used Vue before and are potentially interested in building Vue applications, you can download the first chapter of Fullstack Vue for free!

__________________________________________________________________

👋🏾

Hi! I’m Hassan. I’m the lead author of Fullstack Vue and a Front End developer based out of Toronto, ON. I’m always trying to explain things as simple as possible, so I’ve recently started to blog more about my experiences and give talks on topics I’m passionate about.

You can always connect with me at @djirdehh.

Fullstack.io

Authors of Fullstack React, Fullstack Vue, and ng-book

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

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