Advanced Vuex 4 Tips

Dan Pastori avatar
Dan Pastori September 27th, 2021

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. bookTitletitle). 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!

Support future content

The Ultimate Guide to Building APIs and Single-Page Applications with Laravel + VueJS + Capacitor book cover.

Psst... any earnings that we make off of our book is being reinvested to bringing you more content. If you like what you read, consider getting our book or get sweet perks by becoming a sponsor.

Written By Dan

Dan Pastori avatar Dan Pastori

Builder, creator, and maker. Dan Pastori is a Laravel certified developer with over 10 years experience in full stack development. When you aren't finding Dan exploring new techniques in programming, catch him at the beach or hiking in the National Parks.

Like this? Subscribe

We're privacy advocates. We will never spam you and we only want to send you emails that you actually want to receive. One-click unsubscribes are instantly honored.