Go to content
The Codest
  • About Us
  • Services
  • Our Team
  • Case studies
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
  • About Us
  • Services
  • Our Team
  • Case studies
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
2020-07-21
Software Development

Vuex features you should know if you really care about your store

Wojciech Bak

Vuex features you should know if you really care about your store - Image

Frontend applications, especially the more complex ones, have to process a lot of data. Programmers introduce various design patterns to make their projects readable and maintainable. In most the common scenarios of dealing with an MVC, we want to separate the data from the visual parts of the app.

That's the reason why store has become so useful. It's up to you whether you use React + Redux or Vue + Vuex – the main goal is the same, namely keeping your data structured, accessible and safe at the same time.

In this article, I'm going to show you a few examples of how to keep your Vuex store clean and efficient.

Before we start, let's assume that:

  • you have some experience with modern JavaScript,
  • you basically know what Vue is and how to use props, computed, etc.,
  • you are familiar with Vuex (actions, mutations, etc.) and want to make your apps better.

Vuex, like the majority of core Vue projects, is pretty well-documented and you can find many useful hacks in official docs. We have extracted some essential information from it for you.

A basic Vuex store implementation looks like this:

// main.js

import Vue from 'vue'
import Vuex from 'vuex'
import App from "./App";

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    data: null;
  },
  actions: {
      someAction: ({ commit }, data) {
          commit("SOME_MUTATION", data);
      }
  },
  mutations: {
    SOME_MUTATION (state, data) {
        state.data = data;
    }
  }
});

new Vue({
  el: "#app",
  render: h => h(App),
  store
});

Usually, when your app gets bigger, you have to apply routing, some global directives, plugins, etc. It makes the main.js file much longer and more difficult to read. It's a good practice to keep the store in an external file, like here:

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

const state = {
    data: null;
};

const actions = {
    someAction: ({ commit }, data) {
        commit("SOME_MUTATION", data);
    }
};

const mutations = {
    SOME_MUTATION (state, data) {
        state.data = data;
    }
};

export default new Vuex.Store({
    state,
    actions,
    mutations
});

1. Modules

What should you do when the store.js file gets enormous and difficult to work on? Actually, there is a really cool Vuex feature – modules. They are dedicated to splitting your data into separate files.

Imagine that you work on some corporate app, in which you have few domains of data, for example:

  • user (manage all authorizations and permissions),
  • route parameters (manage global parameters before requests to API),
  • sales (for your SalesMegaChart component visible in a monthly/quarterly/yearly context),
  • orders (visible after clicking on the SalesMegaChart bar).

...and maybe a few more. Now you have serious reasons to introduce some modularity in your store.

First of all, move the store.js file to a newly created store/ directory and rename it index.js. Optionally, if you want to keep everything packed into modules, remove state, actions and mutations from the main file.

// store/index.js

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

Vue.use(Vuex);

export default new Vuex.Store({
    modules: {
        // modules will go here
    }
});

Then, next to the `store/index.js` file, create the first module – `store/user.js`.

import ApiService from '../services/api.service';

const state = {
    loggedIn: false,
    loginError: null,
    user: null
};

const actions = {
    login: async ({ commit }, data) {
        try {
            const response = await ApiService.post('/login', data);
            const { user } = response.data;

            COMMIT("SAVE_USER", user);
            COMMIT("LOGIN_SUCCESS");
        } catch (error) {
            commit("LOGIN_ERROR", error);
        }
    }
};

const mutations = {
    SAVE_USER (state, user) {
        state.user = user;
    },

    LOGIN_SUCCESS (state) {
        state.loggedIn = true;
    },

    LOGIN_ERROR (state, error) {
        state.loginError = error;
        state.loggedIn = false;
    }
};

export const user {
    state,
    actions,
    mutations
}

And now, load the ready module into the main `store/index.js` file:

import Vue from 'vue'
import Vuex from 'vuex'
import { user } from './user';

Vue.use(Vuex);

export default new Vuex.Store({
    modules: {
        user
    }
});

Congratulations! Now you have a really nice-looking store implementation. You can also access the data from the component (e.g., UserProfile.vue) like this:

<template\>
    <div class="user-profile">
        <h2\>{{ user.name }}!</h2>
        <!-- component template goes here -->
    </div>
</template>

<script> import { mapActions } from 'Vuex';

    export default {
        name: 'UserProfile',

        computed: mapState({
            user: state => state.user
            // user: 'user' <-- alternative syntax
        })
    }
</script>

2. Namespaces

Now that you know how to use modules, you should also become familiar with Vuex’s namespacing. On the previous step, we created the store/user.js file with the user module.

The data structure defined in the user.js file is accessible from components, but you can spot that all user data goes directly to the global state context, like here:

computed: mapState({
    user: state => state.user
    // user: 'user' <-- alternative way
})

When you define more modules, you'll probably get confused about which object comes from which module. Then you should use namespaced modules and define them in this way:

export const user {
    namespaced: true, // <-- namespacing!
    state,
    actions,
    mutations
}

From now on, all your user data (state variable from store/user.js file) will be handled under the state.user reference:

computed: mapState({
    user: state => state.user.user
    // user: 'user/user' <-- alternative way
})

A few steps later, you can achieve for the component something like this:

import { mapActions } from 'Vuex';

export default {
    name: 'Dashboard',

    computed: mapState({
        sales: 'sales/data',
        orders: 'orders/data',
        sortBy: 'orders/sortBy',
        loggedIn: 'user/loggedIn'
    }),

    methods: mapActions({
        logout: 'user/logout',
        loadSales: 'sales/load',
        loadOrders: 'orders/load'
    }),

    created() {
        if (this.loggedIn) {
            loadSales();
            loadOrders();
        }
    }
}

Bravo! So fresh, so clean... But don't worry, refactoring never ends. Ready for the next steps?

3. Communication between modules

In the first step, I you showed some action in the user module:

const actions = {
    login: async ({ commit }, data) {
        try {
            const response = await ApiService.post('/login', data);
            const { user } = response.data;

            COMMIT("SAVE_USER", user);
            COMMIT("LOGIN_SUCCESS");
        } catch (error) {
            commit("LOGIN_ERROR", error);
        }
    }
};

In case of failure, we're adding login error to our store – what's next?

Here we have a few options and the choice depends on which option suits your needs better. The simplest way used the v-if directive, thanks to which an error message can be displayed if there's an error in your store.

<template>
    <div class="dashboard"\>
        <!-- dashboard component template -->
        <div
            v-if="error"
            class="error-message"
        > {{ error.message }} </div>
    </div>
</template>
<script> import { mapActions } from 'Vuex';

export default {
    name: 'Dashboard',

    computed: mapState({
        error: "user/loginError"
    })
}
</script>

Again, imagine that you have many modules and each try/catch syntax generates a new error in your store. Obviously, you're going to abuse the DRY rule this way.

How can you make your error handling processes more generic?

Let's define the common module and put some logic in there that would be used globally.

// store/common.js

const state = {
    errors: []
};

const actions = {
    error: {
        root: true,
        handler({ commit }, error) {
            commit("ERROR", error);
        }
    }
},

const mutations = {
    ERROR (state, error) {
        /* this way we're gonna have the newest error on top of the list */
        state.errors = [error, ...state.errors];
    }
};

export const common {
    namespaced: true,
    state,
    mutations
}

Now, we can adapt the user module (and other modules as well):

try {
    // some action
} catch (error) {
    commit("common/ERROR", error, { root: true });
}

or in more elegant way, using our global action:

try {
    // some action
} catch (error) {
    dispatch("error", error);
}

This syntax of commit and dispatch calls seems self-explanatory, but you can read more about these tricks here.

When you have all errors in one place, you can easily load them to your Dashboard component:

computed: mapState({
    errors: 'common/errors'
}),

watch: {
    /* this will be invoked after each "common/ERROR" mutation, where we only add new errors to the store, one by one */
    errors() {
        this.showErrorMessage(this.errors[0]);
    }
}

The previous example with the common module handling errors is already an efficient solution, but you can go even further.

As you can see, we're watching changes on the common/errors array in the store. In cases like these, when you need to determine some action on a particular mutation, you can use Vuex plugins or even Higher Order Components (HOC).

I will discuss the plugins and HOCs in the next article. Meanwhile, thank you for reading this entry, hopefully, you enjoyed the examples we’ve prepared.

Stay tuned and keep coding!

Read more:

- How to improve Vue.js apps? Some practical tips

- GraphQL: lessons learned in production

- Shopify, Spree or Solidus? Check why Ruby on Rails can help you develop your e-commerce

Related articles

Software Development

3 Useful HTML Tags You Might Not Know Even Existed

Nowadays, accessibility (A11y) is crucial on all stages of building custom software products. Starting from the UX/UI design part, it trespasses into advanced levels of building features in code. It provides tons of benefits for...

Jacek Ludzik
Software Development

5 examples of Ruby’s best usage

Have you ever wondered what we can do with Ruby? Well, the sky is probably the limit, but we are happy to talk about some more or less known cases where we can use this powerful language. Let me give you some examples.

Pawel Muszynski
Software Development

Maintaining a Project in PHP: 5 Mistakes to Avoid

More than one article has been written about the mistakes made during the process of running a project, but rarely does one look at the project requirements and manage the risks given the technology chosen.

Sebastian Luczak
Software Development

5 reasons why you will find qualified Ruby developers in Poland

Real Ruby professionals are rare birds on the market. Ruby is not the most popular technology, so companies often struggle with the problem of finding developers who have both high-level skills and deep experience; oh, and by the...

Jakub
Software Development

9 Mistakes to Avoid While Programming in Java

What mistakes should be avoided while programming in Java? In the following piece we answers this question.

Rafal Sawicki
Software Development

A quick dive into Ruby 2.6. What is new?

Released quite recently, Ruby 2.6 brings a bunch of conveniences that may be worth taking a glimpse of.  What is new? Let’s give it a shot!

Patrycja Slabosz

Subscribe to our knowledge base and stay up to date on the expertise from industry.

About us

We are an agile software development company dedicated to empowering our clients' digital transformation projects and ensuring successful IT project delivery.

    United Kingdom - Headquarters

  • Office 303B, 182-184 High Street North E6 2JA London, England

    Poland - Local Tech Hubs

  • Business Link High5ive, Pawia 9, 31-154 Kraków, Poland
  • Brain Embassy, Konstruktorska 11, 02-673 Warsaw, Poland
  • Aleja Grunwaldzka 472B, 80-309 Gdańsk, Poland

    The Codest

  • Home
  • About us
  • Services
  • Case studies
  • Know how
  • Careers

    Services

  • PHP development
  • Java development
  • Python development
  • Ruby on Rails development
  • React Developers
  • Vue Developers
  • TypeScript Developers
  • DevOps
  • QA Engineers

    Resources

  • What are top CTOs and CIOs Challenges? [2022 updated]
  • Facts and Myths about Cooperating with External Software Development Partner
  • From the USA to Europe: Why do American startups decide to relocate to Europe
  • Privacy policy
  • Website terms of use

Copyright © 2022 by The Codest. All rights reserved.

We use cookies on the site for marketing, analytical and statistical purposes. By continuing to use, without changing your privacy settings, our site, you consent to the storage of cookies in your browser. You can always change the cookie settings in your browser. You can find more information in our Privacy Policy.