Uni-directional data flow with Vuex

the state I am in

Colin Fallon
7 min readMar 24, 2017

Vuex is the official Vuejs library for managing application state. It borrows heavily from the concepts of Flux, implementing the same uni-directional data flow approach that I find works so well. The diagram below, slightly simplified from one of Vuex’s own, shows the basic data flow involved.

The Vue components that compose the user interface dispatch actions. These may check certain conditions, access a backend api, and even dispatch other actions. And at some point they may well need to change the application state — this is done by committing a mutation. Only mutations can change the application state. Although, as we’ll see that last statement is not entirely true!

Example application

I have created a very simple application to illustrate the Vuex concepts that we are going to explore in this article, and I think that the best approach would be launch that app in a separate browser window so that you can refer to it as you read on.

In due course if you want to play with the code, then the full example project is available on Github

On the left side of the sample application are some Bootstrap tabs allowing navigation to the three components we are going to look at. This uses vue-router, which was the subject of my previous Vuejs article if you are interested.

The right side of the sample application browser window shows the current application state in Vuex, and allows us to see what effect the actions we take on the left hand side are having on the application state.

Vuex Setup

I have followed the standard approach for Vuex and the Vuex specific files are in the /src/vuex directory. These map on to the Vuex architecture diagram at the start of this article and consist of:

  • store.js which defines, initialises and holds the application state
  • mutation_types.js defines the mutation types supported by the application as constants. This is not necessary, but I find it useful and it mirrors the Flux approach. But don’t worry, no long switch statements necessary!
  • mutations.js exports the functions that are used by actions to actually modify the application state. The constants defined in mutation_types.js are used to identify the functions.
  • actions.js these are functions that are invoked by the components, and in turn they commit the mutations when required.

Component1

You may be startled to discover that I’m going to start with Component1 — so click on that tab if you are following along.

As you enter any data into the input fields you will see that it is immediately reflected in the application state on the right hand side as you type. And if you switch tabs to Component2 or 3, you will see that their input fields display the values that you typed in Component1. So the application state is shared between components, which is what Vuex is all about.

The code for Component1 is very simple and the gist below contains all of it! But it is also very naughty, so don’t just stop reading here and copy that gist or you will probably regret it!!

In the script section, you can see that the 1st line imports a helper function from Vuex called mapState. By passing an array of Vuex state property names as strings to this function, it makes them available as computed properties of your component. Magic!

In Component1, only 1 property from the state is required — the person object, which in turn has the properties of name and profession.

In the script section, this component then uses the Vuejs v-model directive to implement bi-directional binding between the input field value and the application state.

Why so naughty?

The first thing I would point out about the way that Component1 works is that for many use cases it probably doesn’t do what you want your component to do. As soon as you type into the field, the application state is altered — no validation, no easy way to abort the changes, no fun at all!

In addition, Component1 relies on the fact that Javascript objects are manipulated by reference — so when the v-model directive updates the person computed variable that the mapState function assigned from state.person, you are actually changing the state object. It bypasses all of the Vuex good stuff - actions, mutations etc and defeats the whole object of using Vuex in the first place. Plus if you use strict mode, this approach will cause an error as the mutations route is then enforced.

So I don’t recommend using the Component1 approach, but it does give an effective illustration of how Vuex’s application state can allow you to easily share state across your application components.

Component2

Let’s get off the naughty step now and take a look at Component2.

Component2 uses the Vuex mapState helper function in the same way as Component1 to make person available as a computed property. But this time the value of that object is used to initialise localPerson in the local state of Component2 using the following code, which uses Object.assign to make sure it is a copy of state.person rather than pointing to the same object.

data () {
return {
localPerson: Object.assign({}, this.$store.state.person)
}
}

Component2 also uses the similar mapActions Vuex helper function to make the savePerson2 action defined in the Vuex actions.js file available as a method of Component2. And this method is invoked when the Save button is clicked. The template for Component2 is shown below.

You can see that the v-model directive is used again in Component2’s input fields, but this time the bi-directional binding is to local state rather than application state.

So, the local state is initialised from application state. User updates are all applied to local state and then only when the user clicks save are these updates applied to application state. This is the pattern that I follow for many of my Vue components, and it’s illustrated in the diagram below.

Into action

Here’s my code for the savePerson2 action

savePerson2: ({commit, dispatch}, person) => {
dispatch('clearApiError')
api.savePerson(person).then(response => {
commit(types.PERSON_CHANGED, response)
}, _ => {
commit(types.API_FAILED, 'Database update failed')
})
}

I’m mimicking a call to an async api. The api function returns a Promise. If the promise is resolved (i.e. the api call was successful) then the function to commit an application state mutation of type PERSON_CHANGED is called with the response from the api - which is the updated Person. If the promise is rejected (i.e. the api call failed) then the function to commit a mutation of type API_FAILED is called with an extremely helpful error message!

Nb the function that mimics an api is setup to fail 25% of the time, allowing us to see the behaviour of the app when the api fails.

Component3

What more can there be? I hear you ask! Well, it’s all very well updating the application state and then having your component “react” to that new state — but sometimes it is easier to have more direct feedback on the success or otherwise of an action.

Component3 is the same as Component 2, but with an additional button to “Save & Close”. The intention of this button is to save the changes to the database and close the component.

But what if the database update fails? In that case we want to remain in Component3 and display the helpful error message rather than proceed to close the component. This would be difficult to achieve by merely reacting to application state changes, and ideally we want the savePerson action to return an indication of whether the update was succesful or not to he component. And since it’s an async update this is again best done using a Promise.

Component3 uses the action savePerson3 and this wraps the api update in a further Promise, which is then returned to the user interface component.

savePerson3: ({commit, dispatch}, person) => {
dispatch('clearApiError')
return new Promise((resolve, reject) => {
api.savePerson(person).then(response => {
commit(types.PERSON_CHANGED, response)
resolve()
}, _ => {
commit(types.API_FAILED, 'Database update failed')
reject()
})
})
}

As you can see the returned Promise is resolved when the api call is successful, and rejected if not. In the saveClosePerson method of Component3 that is invoked when the “Save & Close” button is clicked, we can use the returned promise as follows to make sure that we only route away from this component (i.e. close it) if the Promise is resolved.

saveClosePerson () {
this.savePerson()
.then(() => this.$router.push('/'), () => {})
}

Try it a few times and you will see that Component 3 is only closed when the database update is successful. As it succeeds 75% of the time — if at first you succeed then try, try, try again.

--

--