Managing multiple central stores with Vuex

Introduction

While building applications, one of the best practices is to make your application architecture component driven by using the “separation of concerns” concept. This also applies when building applications with Vue.

As you follow a component driven architecture, at some point in time, you’ll need to share data among these components.

How can we share data among these components in a Vue application?

Why Vuex ?

In a simple Vue application having just a few components, sharing data can be achieved using Props, Custom Event.

When your components start growing progressively, it’s advisable to introduce a Central Event Bus to serve a standalone service for managing data across the components in your application.

Eventually, your components will build up to form a tree where there will be parents, children, siblings, adjacent siblings etc.

For example, take a registration page which has three different stages. We may come up with four components — 3 to handle the stages and 1 to coordinate and manage the overall operations. You’ll see what I mean in a minute.

Managing data between the parent and children component (and other set of nested components) will get tricky and may easily be messed up while using the aforementioned ways of sharing data — Props and Custom Event

So, what’s the best way to share data among nested components?

A system having nested components

The best way to handle data among these type of components is to introduce Vuex in your application.

Vuex can also be considered as a library implementation tailored specifically for Vue.js to take advantage of its granular reactivity system for efficient updates

Conceptually, Vuex may be pictured as a bucket of water that supplies water based on its content, and to whosoever needs it.

You cannot empty a bucket that’s not filled yet.

Vuex acts more or less like a central store for all components in the application — a bucket from which you can draw water. The store can be accessed by any of the components regardless of the number of (nested) components in an application.

Vuex Abstraction 😃

Let’s take a look at the architecture behind Vuex. If the architectural diagram seems a bit confusing, relax. You are definitely covered!

Before we can access the properties of Vuex, Vue needs to be aware of the external resource, Vuex, before we can use it.

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

Vuex Fragments

For those who have some React.js background, Vuex is a bit similar to aRedux or a Flux implementation. These are all based on the same general idea.

Based on the architectural diagram shown earlier, we’ll discuss the following modules:

1. State

Vuex majors on the idea of a store — where items belonging to the store can be shared easily. This central store holds the state of the application, and the state can either be modified, accessed or retrieved by any components in the application.

A state can also be assumed to be an observer that monitors the life cycle of a property. In this article, the property we’re monitoring is called counter.

A central store acting as a singleton to components

Let’s create a simple application which has two child components (counter and display) and a main component. The counter component has two buttons, increase to add 1 to the counter property, and decrease to reduce the counter by 1. The display component displays the current result of the counter while the main component combines both to make a single component.

Accessing counter state

The goal here is to either update (increase or decrease) the counter or get (display) the current value of the counter property. The state holds all the properties the application has. In this case, it has a counter property which is initially set to 0.

In the display component shown above, the counter property is updated with the current counter state using computed property to display the result as the counter changes.

As simple as the state pattern above is, it can easily get messy when the current state needs to be modified and displayed across multiple components.

Downside of State pattern

In the diagram above, the counter state is modified and displayed in Component R2, R3 and R4 respectively. Assuming the modification is the same, the same piece of code would be repeated in the three components. For example, adding a currency symbol to the counter state before being displayed in the components, the currency symbol would be repeated in all the three (3) components.

How can we stick to DRY (Do not Repeat Yourself) concept while accessing modified state(s) across components?

Another fragment we would look into is the getters, it works with the same concept of get in Javascript, and it returns the fed object.

2. Getters

Getters return the state in the central store. This ensures the state is not accessed directly from the store. It is also easier to modify the state before it’s accessed by any of the components in the application.

Let’s add a currency symbol to counter it being displayed in display component and see how getters work. addCurrencyToCounter (method in getters in snippet above) is accessed by the display component to get the current state of the counter.

To access the counter, addCurrencyToCounter is accessed in the object of $store called getters.

Allowing the component(s) to modify the state directly without tracking which component modified the current state is not ideal. An example is an e-commerce application that has a checkout component, payment component etc. Imagine the itemPrice (state property) is modified by payment component without tracking which component had modified the state. This might result in unforeseen losses.

3. Mutation

Mutation uses the setter approach in getters and setters concept. Before we can access a property, it must have been set. The counter state was initially set to 0. In a situation where the counter needs to be set with a new value, mutation comes into play. It updates (commit) the states in the store.

From the snippet above, the property of the state can be accessed from the parameter of the function. The state update can now be centralized in the central store. Even if the component is the 100th child of the parent, it can update the state and a child from a different parent can also have access to the state.

The commit property can also be accessed from $store to set the state to its current value. Apart from mapGetters used in mapping methods in getters to property names, there is also mapMutationswhich uses the same concept.

mapMutations({
propertyName: 'methodName'
})

Mutation would have been so perfect if it supported both synchronous and asynchronous operations. The methods we have observed so far are synchronous in operation.

Mutation has no chill. It’s only concerned about running a task immediately and making sure the state is accessible instantly.

As your web applications grow bigger, you’d likely want to connect to a remote server. This operation would definitely be treated as asynchronous operation since we can’t tell when the request would be done. If handled directly via mutations, the state would be updated beyond the expected result

mutation handling synchronous activity

How can we handle an asynchronous operation when dealing with mutations?

Since mutations would not run an async operation without messing with the state, its best to keep it out of it. We can always treat it outside mutation, and commit to state in mutation environs when the operation is done. This is where action comes in.

4. Action

Action is another fragment of Vuex. We can more or less call actions as a helper. It’s a function that runs any sort of operation before letting mutation aware of what has been done. Its dispatched from the component and commits (updates) the state of mutation.

how actions work

Now that the action handles the operation, the components have no business interacting with the mutations as we did earlier. The components only have to deal directly with the actions. The actions in central store can be accessed by the components using the object of $store called dispatch.

Let’s take a quick look at how actions are placed in the central store.

Actions do not entirely erase the functions of mutations. As long as the operation we want to run is not async in nature, mutations can always take up the job.

What we have been doing so far is a unidirectional data transfer. The central store has been distributing data to different components.

How do we now handle a bi-directional flow of data between central store and components ?

Getting data from the component, the data can easily be added alongside with the name of action.

this.$store.dispatch('actionName', data);

The second argument is the data (payload) that is sent to the store. It can be any type like string, number etc. I suggest the payload is always in form of an object to ensure consistency. This also provides the chance to pass in multiple data at the same time.

payload = {objValueA, objValueB, .... }

considering an async operation asyncIncrement in the snippet below, which accepts a value from the component and delivers it to the mutation (commit) to update the state.

Conclusion

Vuex gives you the flexibility to manage multiple central stores based on the type of your project structure. You can also group your stores into modules. The modules act like a container to group more than one central store. This helps to manage stores properly belonging to different groups. Also, its advisable to group method names which are created in mutations, actions and getters into a single object.