Currently, we are rebuilding part of an application that is written in plain JS/jQuery. Although functioning pretty well, the current code is difficult to maintain and new functionality is not easy to incorporate. We’ll use Vue to improve it but does this project require Vuex? I think it does and in this post I will explain our reasoning. Here is a breakdown of this post:
- The difficulties you experience when an app grows
- The way I think Vuex can solve these difficulties
- Some explanation on how ‘things’ (read Vuex store, actions, mutations, getters) are integrated in your app and components.
A simple Vue app without state management
Suppose we write a simple Vue app that generates a page as shown below, for example for a blog:
The list inside the Content component may contain blog posts for example. My assumption here is that the Main component contains all state in its data property and the child components are presentational only: they are passed a bunch of props and render themselves.
But what if several components have some user interaction on them? For example
- Buttons below the list for pagination. Clicking those buttons means fetching a new set of posts from the server and rerender the list with the updated data.
- Commenting on a post. The comment should be stored in the database on the server and shown on the post page.
- The footer may have a ‘sign up form’ for our newsletter and upon subscription we need to show a message to the user.
Although these interactions are rather easy to implement you’ll need to create some methods on the Main component to serve as callbacks for events dispatched from the child components. If communication between child components is required, you’ll need a global Event bus to achieve this. With a lot of user interactions this quickly becomes complex and gets out of hand.
The difficulty lies in reasoning about the app. People tend to quickly forget which component is emitting what and which method is responsible for handling the event. Even more so when you are working in a team.
Note: This only holds for interfaces with a lot of interaction! If you are just starting out with a small app, go ahead and use these methods.
So how can Vuex help us?
Vuex is a state management pattern. It extracts the global state of an application into a store and this state can only be changed by mutations. This means we are able to separate the data and the views. However, the two remain coupled by user interactions. A typical Vuex flow may be described as follows:
- The state is defined as a single object in the store
- The state is rendered following the template(s) of the individual components
- A user interacts with the app and dispatches (triggers) an action
- The action may do some asynchronous stuff but will eventually commit one or more mutations
- A mutation accepts the current state and a payload (some data passed along) and computes the new state
- Vue uses the virtual DOM to compare the new and old state and decides whether it should rerender.
This flow is started off with an initial state.
With this in mind, we can overcome the difficulties discussed before. We just need to create the store with an initial state and define actions and mutations. I will not address this part of Vuex as it is discussed in detail in the official Vuex docs. Additionally, these links may help you.
However, the questions that remain are
- How to use this state in our templates and
- How do we trigger actions?
Meet mapGetters
In general, when you pass the store to your root Vue component, it is available in all child components by this.$store. Since the state is a property of the store object you can reference it by this.$store.state. So if your state has a property loading, you can simply retrieve it from the store and use it in your template as $store.state.loading.
However, when your state is a deeply nested object, it becomes tedious to type $store.state.mydata.posts.latest when trying to render the list of latest posts. Meet store getters and mapGetters!
A store getter is simply a “computed property for the state”. Hence, you may define the above example as
latestPosts: state => state.mydata.posts.latest
When you need this computed property in a template inside a component, make sure to
import { mapGetters } from ‘vuex’
and add the getter to your computed properties like so:
computed: mapGetters([‘latestPosts’])
If you already have local computed properties defined on the component, you can use the object spread operator (ES6) to combine them in one object
computed: { localComputedProp() { ... }, ...mapGetters([ 'latestPosts' ]) }
Hi there, mapActions
mapActions works roughly the same way as mapGetters. The actions are dispatched by the store with $store.dispatch(‘submitComment’)but mapActions simply maps this so you can use submitComment in your template. If we would have 2 actions in the store, toggleLoading and submitComment, you would apply mapActions like this:
import { mapActions } from ‘vuex’ methods: mapActions([ ‘toggleLoading’, ‘submitComment’ ])
Again, combining these store actions with local component methods is easy using the spread object operator.
Conclusion
When starting a new project it is good practice to take a moment to think about the flow in your app. Is it a rather static app with nearly no user interactions? Then you probably don’t need Vuex as there won’t be much of a state to handle anyway. Maybe you don’t even need Vue: you might get away with a little plain old JS.
But is your project an interaction heavy platform with users doing all kind of things? Then sure you will benefit from implementing Vuex. At first it may take some more time to grasp the pattern and syntax. But when implemented correctly, it will lead to a much better maintainable code base that is extendable in the future!