Advanced Vuex 4 Tips
So we covered the basics in “Beginning Vuex 4 with Vue 3“, but if you are building a larger app, you might have already run into some issues. Or maybe you started thinking, there has to be a better way? When you start building state in larger applications, complex forms, or individual pages that could be an app themselves, you will end up wanting to store more and more in the state. There are a ton of advanced Vuex 4 practices that can help make this process even easier. Now that we have gotten our feet wet with Vuex, let’s jump into some more of the advanced Vuex 4 use cases and some other awesome features of the state management system!
Map Helpers
The map helpers are some of my favorite features of Vuex. This allows you to essentially short hand access the 4 features of your modules (state, getters, mutations, and actions). Each piece has their own map
helper. For example, state
has the mapState
helper method that you can import. These helpers make managing state easy to do in any component.
mapState
Let’s start with the mapState
helper. To add the mapState
helper to your component, add the following to your imports:
import { mapState } from 'vuex';
You now have the mapState
method available to use within your component. To use this helper method you have to add it to your computed
properties like this:
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState({
title: state => state.title
}),
}
}
</script>
You now have access directly to your title
within your component and can access it in the template or in your methods using this.title
. You will be able to watch for changes locally and you will even be able to use these in your v-model
assignment. There are a few gotchas with that, so there’s a whole different section.
mapGetters and mapMutations
To be honest, I don’t use these helpers often since I tend to use mapState
or treat the piece of state as a model. These two helpers are good to know exist though since any of these map helper functions can come in handy.
There are two things to note if you are using either of these helpers. First, you have to include them like mapState
on top of the component:
import { mapGetters } from 'vuex'
and
import { mapMutations } from 'vuex'
The next thing to note is if you are using mapGetters
you use it inside of your computed
property:
<script>
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters([
'getUser'
]),
}
}
</script>
You also pass an array with the names of the getters you wish to use locally. Then you can call this.getUser
to access the user.
With the mapMutations
, since these are functions, they map inside of the methods
property:
<script>
import { mapMutations } from 'vuex';
export default {
methods: {
...mapMutations([
'setUser'
]),
}
}
</script>
You can then call the method to set the user like this: this.setUser( user )
and it will perform the mutation to update the user in the store.
When we run into using the state as a v-model
you will see why we don’t usually use these helpers much. If you want more information, the Vuex documentation on mapGetters and mapMutations provides a lot more information.
mapActions
To be honest, I use actions sparingly with my Vuex modules. Reason being is that when you map state locally in a component, I usually perform the methods I need within the component instead of the action. With that being said if I need to perform the same method multiple times from multiple components, I will create an action and then map the action locally. In the Beginning Vuex 4 with Vue 3 I mentioned this with a next()
method on a playlist. This could be called anywhere from multiple components so creating an action would be helpful.
Instead of creating a next()
method locally in each component and calling this.next()
:
export default {
methods: {
next(){
this.$store.dispatch('nextSong');
}
}
}
We can use mapActions
helper to do that for us. First, we can import the mapActions
helper:
import { mapActions } from 'vuex'
Next, we can add it to our methods
:
<script>
import { mapActions } from 'vuex';
export default {
methods: {
...mapActions( [
'next'
]),
}
}
</script>
Now we can call this.next()
as much as we want within our components and we don’t have to repeat code!
Using Multiple Map Helpers
We will touch on namespacing in a few sections, but one feature I’d like to touch on before we leave our discussion of map helpers is mapping multiple pieces of state in a component. Say you have a piece of state that is a user and some settings that you want to map in a single component.
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState('user', {
user: state => state.user
}),
...mapState('settings', {
title: state => state.title
}),
}
}
</script>
You now can access this.user
and this.title
and they both map to their appropriate state. I’ve definitely run into multiple scenarios where I’ve had to map multiple pieces of state in a component. Especially when working with a large form where pieces are namespaced. The process is similar for all map helpers, so actions
, getters
and mutations
are the same.
Before we get into namespacing and addressing the issues such as possible naming conflicts, let’s touch on using Vuex state as a v-model
.
Using Vuex State as a v-model
So now we know we have all of these amazing helpers available for our use, let’s go one step further and use the mapped state as a v-model
on a form input.
Quick tangent. I’ve built some massive forms that require tons of dynamic computation even before persisting to the database (ie. Entering an address and when every field is validated, grabbing latitude and longitude from Google). When creating these massive forms I ended up with a page or a component that was well over 2000 lines of code. This is unmaintainable.
I then divided the form into small, maintainable components. These Vue components were not meant to be re-usable but scoped simply on the form page. That brought us to the next issue. Passing all of the properties for the form down to each component and watching for changes. This became a headache instantly. Luckily, Vuex state is the perfect tool for the job. When dealing with a large form, I actually make a piece of state that is monitored by all the shared components that make up the form. This way I can easily pass data between the components, have small maintainable form pieces, and handle massively complex computations.
With that being said, using your mapped state as a v-model
is extremely important.
So with mapped state, you can easily reference the state within components. Maybe this for display or to use in calculation. But there are also times where you want to harness state reactively through a v-model
.
Let’s say you have a piece of state that’s a title
and you want to bind it to an input field:
<input type="text" v-model="title"/>
This title is shared across multiple components through state. To use this as a v-model
you will have to set up your computed
property on the component to look like:
<script>
export default {
computed: {
title: {
get(){
return this.$store.getters[ 'getTitle' ];
},
set( value ){
return this.$store.commit( 'setTitle', value );
}
}
}
}
</script>
What you are doing is explicitly defining a two-way binding that allows you to load the title through a getter
and also call the setTitle
mutation when the value updates. You can now use your Vuex state within your v-model
and most importantly, spread out massive forms across multiple components!
Namespacing Vuex Modules
Namespacing Vuex modules is extremely important as your app grows. What namespacing allows you to do is neatly nest modules and access properties within the scope of a name. When you get into larger apps, you could have duplicate state names which would conflict and cause a nightmare to maintain.
For example, say you are managing media and you have a piece of state named title
and a mutation called setTitle
. Is this title for a movie, book, or song? You can’t really decipher that. You also don’t want to have a million pieces of top level state where you have getters like getBookTitle()
and getVideoTitle()
. Your module will become overwhelmingly large.
Namespacing solves this problem. To namespace a Vuex module you have to add the namespaced
option set to true
to the top of your module:
export const book = {
namespaced: true,
state: () => ({
title: ''
}),
mutations: {
setTitle( state, text ){
state.title = text;
}
},
getters: {
getTitle( state ){
return state.title;
}
}
}
Now, if you have two modules that are namespaced you can register them in your store:
import { book } from './Modules/book.js';
import { movie } from './Modules/movie.js';
const store = createStore({
modules: {
book,
movie
}
});
When it comes time to access the title of the module you want you can call:
this.$store.getters['book/getTitle'];
or
this.$store.getters['movie/getTitle'];
And get the title you want! Same goes with mutations and actions. You reference the specific feature through a namespace (similar to a file structure) reference. The namespace is the exported function movie
is the name of the namespace.
You can even do this with the map helpers if you pass the namespace as a first parameter:
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState('book', {
title: state => state.title
})
},
}
</script>
The first argument is the namespace (in this case book
) and then you can begin your mapping.
You can even map multiple namespaces in the same component:
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState('book', {
bookTitle: state => state.title
}),
...mapState('movie', {
movieTitle: state => state.title
})
},
}
</script>
Now this.bookTitle
is mapped to the book title state and this.movieTitle
is mapped to the movie title state. Quick note, you can assign custom names locally and map it to state of a different name (ie. bookTitle
≠ title
). This flexibility comes in handy as your app grows. In the next section we will be getting into some seriously large scale mapping and namespacing.
Nesting Namespaced Vuex Modules
To cap this tutorial off, let’s get into nested namespacing with Vuex modules. Let’s say we have an ultra large app or a page that has a ton of settings. Your Vuex module is getting un-maintainable (well over 1000 lines of code… it happens). You will need to split it up. To keep structure, the first thing you want to try is namespacing. But sometimes that might not be enough on its own. You can move to nested namespaced Vuex modules!
What this means, is say you are dealing with a settings form and each setting belongs to a group. We did this with AirStudio editor. You can nest a namespace to make your modules extremely easy to maintain.
What I mean by that is say you have a settings
module. In your settings
module you have settings for a canvas
which has a background color, width, height. You also have settings for a title
which contains a font, color, size, and weight. These are small settings that are used throughout the massive editor page. If you put them all into a single editor
namespace, you will end up with an un-maintainable editor module that every time you want to add a setting it becomes a chore to maintain. Also assume there are other modules such as video, user, etc. that live on the same level as the editor
module.
To make these maintainable, you divide up the module into sub modules. For example, I have an editor
module, that has a sub module of canvas
and title
. Now within each of these modules, I store the localized state. Let’s take a look.
First, I register my editor
module with the store:
import { editor } from './Store/editor.js';
const store = createStore({
modules: {
editor
}
})
Notice that I didn’t include the canvas
or title
sub modules? That’s alright, those are coming soon.
Next, I create my editor
module:
import { title } from './editor/title.js';
import { canvas } from './editor/canvas.js';
export const editor = {
namespaced: true,
modules: {
title,
canvas
}
}
This is where the magic begins to happen! I then have sub modules which we will show next, that are imported into my editor
module. I also like to organize these modules in a directory based on their parent modules. In this case, I have an editor
directory that contains the title
and canvas
modules.
I then namespace the editor
module, but then I register the sub modules in the modules
property. Now I have a nested module that keeps our code nice and clean!
So our title
module can look like this:
export const title = {
namespaced: true,
state: () => ({
font: '',
color: '',
size: '',
weight: ''
}),
mutations: {
setFont( state, font ){
state.font = font;
}
},
getters: {
getFont( state ){
return state.font;
}
}
}
and our canvas
module:
export const canvas = {
namespaced: true,
state: () => ({
color: '',
width: '',
height: ''
}),
mutations: {
setColor( state, color ){
state.color = color;
}
},
getters: {
getColor( state ){
return state.color;
}
}
}
It’s important to note the namespaced: true
on top of all of these modules. There are so many benefits to this. First, each module is nice and tidy and easily maintained. Second, they are nested within the main editor
module so you can scope which properties need to be used.
To map a nested module’s state, simply extend the namespace to look like:
import { mapState } from 'vuex';
export default {
computed: {
...mapState('editor/title', {
font: state => state.font
}
}
}
The same goes for all of the other mapped helper functions. Now you have a nice clean way to manage the state you need.
If you are not mapping state and need to dispatch or commit an action, you can access the nested and namespaced module like this:
this.$store.commit('editor/title/setColor', color);
I hope this helps shed some light on the power of Vuex and what it can do! It’s extremely powerful for organizing shared data across components or pages in an SPA. If you have any questions, by all means reach out, I love discussing Vue and Vuex!