12
votes

Im building my app with VueJS and Vuex and I'm facing the issue when I have Multiple modules using the same data fields. Its about API configuration like dat.

getUsers ({ state, commit }) {
    axios.get(urls.API_USER_URL).then( response => {
        let data = response.data;
        parseApi(state, data, 'user');

    }).catch( err => {
        console.log('getUser error: ', err);
    })
},

And another function in other Modules is like

getPosts ({ state, commit }) {
    axios.get(urls.API_POST_URL).then( response => {
        let data = response.data;
        parseApi(state, data, 'posts');

    }).catch( err => {
        console.log('getUser error: ', err);
    })
},

I would like to know if I can just inheritence my Module and add additional datafields / functions in there?

My every module would have message and status field which I getting in response of my API.

export default {
    state : {
        message : "",
        status : 0
    },
    parseApi: function(state, data, property) {
        if (data.hasOwnProperty('message')) {
            state.message = data.message;
        }
        if (data.hasOwnProperty('status')) {
            state.status = data.status;
        }
        if (data.hasOwnProperty(property)) {
            state[property] = data[property];
        }
    }
}

It would be something like that.

Is there a way to write this code once and have it in every module Im using?

EDITED:

I even cant get this apiParse function in there, I need to make muttation for those fields. But repeting it all time is pointless... Any advices?

4
Did you try it? If so, what happened?Ohgodwhy
It doesn't work ofc. I dont know how could I actually hanlde that. any advices?Canor

4 Answers

20
votes

I put my reusable vuex code in small classes. E.g.

crud.js

export default class {
    constructor ( endpoint ) {
       this.state = {
          endpoint: endpoint,
          meta:     {},
          status:   null,
          known:    [],
          currentId: null,
       };
       this.getters = {
          id: state => id => state.known.find( o => o.id === id )
       };
       this.actions = {
          async store( context, payload ) {
               *(call to API)*
          },
          async update( context, payload ) {
               *(call to API)*
          },
          *...etc*
      };
      this.mutations = {
         STORED(state, item) {
            state.known.push(item);
         },
         *...etc*
      };
   }
}

Then I can use it in all of my modules:

user.module.js

import Crud from '/crud';
var crud = new Crud('/api/users');

const state = {
   ...crud.state,
};
const getters = {
   ...crud.getters,
};
const actions = {
   ...crud.actions,
};
const mutations = {
   ...crud.mutations,
};

export default {
   namespaced: true,
   state,
   getters,
   actions,
   mutations
};
5
votes

Developing a little bit more Erin's response, you can define a base class with common features like this:

export default class BaseModule {
    protected state() {
        return {
            isLoading: false,
        };
    };
    protected getters() {
        return {
            isLoading(s) {
                return s.isLoading;
            },
        };
    };
    protected actions() {
        return {};
    };
    protected mutations() {
        return {
            [START_TRANSACTION]: (s) => {
                s.isLoading = true;
            },
            [END_TRANSACTION]: (s) => {
                s.isLoading = false;
            },
        };
    }
    protected modules() {
        return {};
    };

    public getModule = () => {
        return {
            namespaced: true,
            state: this.state(),
            getters: this.getters(),
            actions: this.actions(),
            mutations: this.mutations(),
            modules: this.modules(),
        };
    }
}

You can now extend/override only the parts you need in derived classes, with class inheritance; for example, if you need to extend the modules...:

import BaseModule from './BaseModule';
import rowDensity from '@/store/modules/reusable/rowDensity';

export default class ItemListModule extends BaseModule {  
  protected modules() {
    return {
      ...super.modules(),
      rowDensity,
    };
  };
}

Finally, to use them as modules in the store, you can instantiate them and call .getModule():

import Vue from 'vue';
import Vuex from 'vuex';
import ItemListModule from './modules/ItemListModule';

Vue.use(Vuex);

const debug = process.env.NODE_ENV !== 'production';

export const MODULE_NAMESPACES = {
  List: 'list',
};

export default new Vuex.Store({
  modules: {
    [MODULE_NAMESPACES.List]: new ItemListModule().getModule(),
  },
  strict: debug,
});
4
votes

I figured out some inheritance with the state fields according to:

https://vuex.vuejs.org/en/modules.html#namespacing

export default {
    namespaced: true,
    state,
    getters,
    actions,
    mutations,
    modules : {
        apiResponses
    }
}

I exported apiResponses module after the module user with namespaced and next i did the same thing with posts.

The namespaces inherited those message / status states and their mutations and which i just called in my user and post module. Now they are working corectly.

My message muttation form apiResponses:

[types.SET_MESSAGE] (state, message) {
    state.message = message;
},

Works inside actions of my user modules

if (data.hasOwnProperty('message')) {
    commit(types.SET_MESSAGE, data.message);
}

Then in my commponent I just call.

    computed: {
        ...mapGetters({
            user : 'user/user',
            userMessage : 'user/message',
            post: 'post/monitoring',
            postMessage : 'post/message',

        }),
    },

EDITED

The last part of my issue is like that.

I got action inside apiResponse Module

let actions = {
    getResponseParsed({commit}, payload) {
        console.log(payload)
        if (payload.data.hasOwnProperty('message')) {
            commit(types.SET_MESSAGE, payload.data.message);
        }
        if (payload.data.hasOwnProperty('status')) {
            commit(types.SET_STATUS, payload.data.status);
        }
        if (payload.data.hasOwnProperty(payload.property)) {
            commit(payload.mutation, payload.data[payload.property]);
        }
    }
}

And then inside my user and other module i called it like:

getUser ({ state, commit, dispatch }) {
    axios.get(urls.API_GET_USER_URL).then( response => {
        let data = response.data;

        dispatch('getResponseParsed', {
            data : data,
            mutation : types.SET_USER,
            property : 'user'
        });

    });
},

And the last thing, we need to make this new module reusable to according to docs we need to create it like a components.

export default {
    state() {
        return {
            message : '',
            status : 0,
        }
    },
    getters,
    mutations,
    actions
}

With the state as function :)

Hope somone else got same issue :D

0
votes

As a personal challenge I wanted to be able to create a pure ES6 class that could express this need (meaning no annotation allowed). I thus created an AbstractModule class defining the high level operations:

export default class AbstractModule {
    constructor(namespaced = true) {
        this.namespaced = namespaced;
    }

    _state () {
        return {}
    }

    _mutations () {
        return {}
    }

    _actions () {
        return {}
    }

    _getters () {
        return {}
    }

    static _exportMethodList (instance, methods) {
        let result = {};
        // Process methods when specified as array
        if (Array.isArray(methods)) {
            for (let method of methods) {
                if (typeof method === 'string') {
                    result[method] = instance[method].bind(instance);
                }

                if (typeof method === 'function') {
                    result[method.name] = method.bind(instance);
                }

                // else ignore
            }
        }

        // Process methods when specified as plain object
        if (typeof methods === "object") {
            for (const [name, method] of Object.entries(methods)) {
                if (typeof method === 'string') {
                    result[name] = instance[method].bind(instance);
                }

                if (typeof method === 'function') {
                    result[name] = method.bind(instance);
                }
            }
        }

        // Process methods when specified as single string
        if (typeof methods === 'string') {
            result[name] = instance[methods].bind(instance);
        }

        // Process methods when specified as single callback
        if (typeof methods === 'function') {
            result[name] = methods.bind(instance);
        }

        return result;
    }

    static module() {
        let instance = new this();
        console.log(instance);

        return {
            namespaced: instance.namespaced,
            state: instance._state(),
            mutations: AbstractModule._exportMethodList(instance, instance._mutations()),
            actions: AbstractModule._exportMethodList(instance, instance._actions()),
            getters: AbstractModule._exportMethodList(instance, instance._getters())
        }
    }
}

From this I created my own class module by redefining the parent methods I wanted to customize this way:

export default class QuestionModule extends AbstractModule{
    constructor(question) {
        super();
        this.question = question;
    }

    selectLine (state, line) {
        this.question.selectLine(line);
    }

    unselectLine (state, line) {
        this.question.unselectLine(line);
    }

    submit ({ state, commit, rootState }) {
        /** API call */
    }

    _state () {
        return this.question;
    }

    _mutations () {
        return [this.selectLine, this.unselectLine, this.validate];
    }

    _actions () {
        return this.submit;
    }
}

Final step is to declare my class module into the Vuex store (through a call to the module static method):

const store = new Vuex.Store({
  modules: {
      question: QuestionModule.module()
  },
  strict: process.env.NODE_ENV !== 'production'
});