Vuex rootActions with several modules - vue.js

I have a vuejs on client side, where I use vuex for state management. I have several modules (in separate files) but some of the modules, has very similar actions, that's why I want to create dry code.
My goal: Creating a root action, what can be called by every modules.
My main vuex looks like this:
export default new Vuex.Store({
actions: {
rootactions,
},
modules: {
users,
classes,
studentgroups,
// ... other stuff
}
})
How should I refer the rootactions methods?
Thanks for the responses in advance!

"To dispatch actions or commit mutations in the global namespace, pass { root: true } as the 3rd argument to dispatch and commit."
{
root: true
}
https://vuex.vuejs.org/guide/modules.html

How about to create a separate file RootActions.js with the common actions
export default {
action1: () => {
//
},
action2: () => {
//
},
}
And then import it in your module file e.g. User.js
import RootActions from './RootActions'
const state = {
// state object
}
const mutations = {
// mutations object
}
const actions = {
// actions object
}
export default {
namespaced: true,
state,
mutations,
actions: Object.assign(RootActions, actions)
}

Related

In Nuxt, how to mutate a Vuex state in one module from the other?

I tried many things mentioned on the portal but nothing seems to work for me so posting here for some work-around.
I have 2 modules within my Nuxtjs application folder store\modules: ModuleA and ModuleB. For some verification in ModuleB I would like to access the state from ModuleA but for some reason, it's failing.
I tried rootScope, import etc but it did not work for me.
My state/modules/ModuleA.js:
export const state = () => ({
eventType: 'MyEvent',
})
export const mutations = {
eventTypePopulator (state, event) {
state.eventType = event
},
}
My state/modules/ModuleB.js:
export const state = () => ({
input: {}
})
export const mutations = {
jsonPreparation ({state, rootState}, payload) {
console.log(rootState.eventType)
// console.log($store.state.ModuleA.eventType)
}
}
I tried to import ModuleA into ModuleB and use it but that also did not work for me. Can someone please help me how can I access the state from one Module in another Module within Nuxtjs/Vuejs
As shown in the API reference, rootState is available in actions and getters.
It did not found any way of using it directly into a mutation.
Meanwhile, this can be achieved by passing it as a param to the mutation like this
ModuleB.js
const mutations = {
NICE_TASTY_MUTATION: (_state, { rootState, payload }) => {
// _state is not used here because it's moduleB's local state
rootState['moduleA'].eventType = payload
},
}
const actions = {
async myCoolAction({ commit, rootState }, { ...}) {
commit('NICE_TASTY_MUTATION', {
rootState,
payload: 'some-stuff'
})
}
}
And this could be called in a .vue file with something like this
methods: {
...mapActions('ModuleB', ['myCoolAction']),
}
...
await this.myCoolAction()
PS: IMO the convention is more to name the file module_b.js.

"[vuex] state field foo was overridden by a module with the same name at foobar" with deepmerge helper function in jest

I'm using a helper function to create a store inside my jests. The helper function uses deepmerge to merge the basic configuration with a customized configuration. This results in multiple console warnings
[vuex] state field "cart" was overridden by a module with the same name at "cart"
[vuex] state field "customer" was overridden by a module with the same name at "customer"
[vuex] state field "checkout" was overridden by a module with the same name at "checkout"
store.js (Reduced to a minimum for presentation purpose)
import cart from './modules/cart'
import checkout from './modules/checkout'
import customer from './modules/customer'
Vue.use(Vuex)
export const config = {
modules: {
cart,
customer,
checkout,
},
}
export default new Vuex.Store(config)
test-utils.js
import merge from 'deepmerge'
import { config as storeConfig } from './vuex/store'
// merge basic config with custom config
export const createStore = config => {
const combinedConfig = (config)
? merge(storeConfig, config)
: storeConfig
return new Vuex.Store(combinedConfig)
}
making use of the helper function inside
somejest.test.js
import { createStore } from 'test-utils'
const wrapper = mount(ShippingComponent, {
store: createStore({
modules: {
checkout: {
state: {
availableShippingMethods: {
flatrate: {
carrier_title: 'Flat Rate',
},
},
},
},
},
}),
localVue,
})
How do I solve the console warning?
I believe the warning is somewhat misleading in this case. It is technically true, just not helpful.
The following code will generate the same warning. It doesn't use deepmerge, vue-test-utils or jest but I believe the root cause is the same as in the original question:
const config = {
state: {},
modules: {
customer: {}
}
}
const store1 = new Vuex.Store(config)
const store2 = new Vuex.Store(config)
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<script src="https://unpkg.com/vuex#3.4.0/dist/vuex.js"></script>
There are two key parts of this example that are required to trigger the warning:
Multiple stores.
A root state object in the config.
The code in the question definitely has multiple stores. One is created at the end of store.js and the other is created by createStore.
The question doesn't show a root state object, but it does mention that the code has been reduced. I'm assuming that the full code does have this object.
So why does this trigger that warning?
Module state is stored within the root state object. Even though the module in my example doesn't explicitly have any state it does still exist. This state will be stored at state.customer. So when the first store gets created it adds a customer property to that root state object.
So far there's no problem.
However, when the second store gets created it uses the same root state object. Making a copy or merging the config at this stage won't help because the copied state will also have the customer property. The second store also tries to add customer to the root state. However, it finds that the property already exists, gets confused and logs a warning.
There is some coverage of this in the official documentation:
https://vuex.vuejs.org/guide/modules.html#module-reuse
The easiest way to fix this is to use a function for the root state instead:
state: () => ({ /* all the state you currently have */ }),
Each store will call that function and get its own copy of the state. It's just the same as using a data function for a component.
If you don't actually need root state you could also fix it by just removing it altogether. If no state is specified then Vuex will create a new root state object each time.
It is logged when a property name within the state conflicts with the name of a module, like so:
new Vuex.Store({
state: {
foo: 'bar'
},
modules: {
foo: {}
}
})
therefore this raises the warning.
new Vuex.Store(({
state: {
cart: '',
customer: '',
checkout: ''
},
modules: {
cart: {},
customer: {},
checkout: {},
}
}))
its most likely here
export const createStore = config => {
const combinedConfig = (config)
? merge(storeConfig, config)
: storeConfig
return new Vuex.Store(combinedConfig)
}
from the source code of vuex, it helps indicate where these errors are being raised for logging.
If you run the app in production, you know that this warning wont be raised... or you could potentially intercept the warning and immediately return;
vuex source code
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
if (__DEV__) {
if (moduleName in parentState) {
console.warn(
`[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
)
}
}
Vue.set(parentState, moduleName, module.state)
})
vuex tests
jest.spyOn(console, 'warn').mockImplementation()
const store = new Vuex.Store({
modules: {
foo: {
state () {
return { value: 1 }
},
modules: {
value: {
state: () => 2
}
}
}
}
})
expect(store.state.foo.value).toBe(2)
expect(console.warn).toHaveBeenCalledWith(
`[vuex] state field "value" was overridden by a module with the same name at "foo.value"`
)
Well, I believe there is no need for using deepmerge in test-utils.ts. It is better we use Vuex itself to handle the merging of the module instead of merging it with other methods.
If you see the documentation for Vuex testing with Jest on mocking modules
you need to pass the module which is required.
import { createStore, createLocalVue } from 'test-utils';
import Vuex from 'vuex';
const localVue = createLocalVue()
localVue.use(Vuex);
// mock the store in beforeEach
describe('MyComponent.vue', () => {
let actions
let state
let store
beforeEach(() => {
state = {
availableShippingMethods: {
flatrate: {
carrier_title: 'Flat Rate',
},
},
}
actions = {
moduleActionClick: jest.fn()
}
store = new Vuex.Store({
modules: {
checkout: {
state,
actions,
getters: myModule.getters // you can get your getters from store. No need to mock those
}
}
})
})
});
whistle, In test cases:
const wrapper = shallowMount(MyComponent, { store, localVue })
Hope this helps!

Correct setup and use of VUEX store mutation-types

I'm developing a Chrome extension using one of the Vue js boilerplates from Github. The default boilerplate setup is as follows:
store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import mutations from './mutations';
import * as actions from './actions'; // all actions are imported as separate vars
Vue.use(Vuex);
export default new Vuex.Store({
state: { },
mutations,
actions
});
Then in actions.js
import * as types from './mutation-types';
export const setFoo = ({ commit }, payload) => {
commit(types.SET_FOO, payload); // SET_FOO is defined in the mutation-types file
};
I think the above approach lacks a fundamental reason why we want to use mutation types file - to avoid retyping the names for mutations and actions.
So instead, I came up with a different approach:
store/index.js
...
import actions from './actions'; // actions are imported as separate functions
...
Then in actions.js
import * as types from './mutation-types';
export default {
[types.UPDATE_FOO] ({commit}, payload) {
commit(types.UPDATE_FOO, payload);
}
}
Then anywhere in the extension, we could also import mutation-types and dispatch actions using const names like so:
store.dispatch(types.UPDATE_FOO, 'some value');
The second approach seems to be more practical in terms of naming and then dispatching/committing our actions/mutations. Or could there be any issues with the latest?
Which of the above, would be generally better practice?
The first approach is preferable, but it's completely up to you. Similar approach is used in official Vuex docs.
Refrence
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// we can use the ES2015 computed property name feature
// to use a constant as the function name
[SOME_MUTATION] (state) {
// mutate state
}
}
})
// actions.js
actions: {
checkout ({ commit, state }, products) {
// save the items currently in the cart
const savedCartItems = [...state.cart.added]
// send out checkout request, and optimistically
// clear the cart
commit(types.CHECKOUT_REQUEST)
// the shop API accepts a success callback and a failure callback
shop.buyProducts(
products,
// handle success
() => commit(types.CHECKOUT_SUCCESS),
// handle failure
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}

Using same action in two vuex modules to separate logging logic

So I am developering nuxt app, using vuex as a store.
I came up with idea of having some logging (simple rest api requests to laravel backend), to log user's actions.
I found out that if I have two actions that are named the same in two different modules, both will be executed.
Is that acceptable practice? Or is it undocumented behaviour which will be fixed and removed?
Quick scheme on what is happening:
store/index.js
import logging from './logging';
import search from './search';
const store = () =>
new Vuex.Store({
modules: {
logging,
search,
}
});
export default store;
store/search.js
const actions = {
search(state, query) {
// some search request and processing results
}
};
const search = {
state,
mutations,
actions,
};
export default search;
store/logging.js
const actions = {
search(state, query) {
log(...)
}
};
const logging = {
state: {},
mutations: {},
actions,
};
export default logging;
I see two ways:
use constants
use namespased modules
Constants:
create file: constants/store.js
export const LOGGING_SEARCH = 'logging search';
export const SEARCH = 'search';
then use it, e.g.:
import { LOGGING_SEARCH } from '../constants/store.js'
const actions = {
[LOGGING_SEARCH](state, query) {
log(...)
}
};
const logging = {
state: {},
mutations: {},
actions,
};
export default logging;
and call it
import { LOGGING_SEARCH } from '../constants/store.js'
store.dispatch(LOGGING_SEARCH, 'query');
namespased modules doc
store/logging.js
const actions = {
search(state, query) {
log(...)
}
};
const logging = {
namespased: true,
state: {},
mutations: {},
actions,
};
export default logging;
and call it
store.dispatch('logging/search', 'query');
Namespased modules are really helpfull especially with helpers

How can I use Vuex modules state without nested objects?

I'm trying to convert existing Vuex state to modules. I'm trying to use modules like this:
const moduleA = {
state: {},
mutations: {
update (state, payload) { // payload is an object
state = payload // doesn't work
}
},
I'm registering modules:
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
The question is: is it possible to use state without nesting as I'm trying to do? Or the only way is to have something like:
const moduleA = {
state: {
one: {}
},
mutations: {
update (state, payload) {
state.one = payload // it works
},
},
}
Ideally, I'd like to have the same structure as it was before and being able to get the state as state.a instead of state.a.one
Thank you
Yes you can -- just refactor the modules into separate files and then use an index file to bring them all together:
import Vue from 'vue'
import Vuex from 'vuex'
import users from './store.users.js'
import entries from './store.entries.js'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
users,
entries
}
})
export default store
Then in each module's file (e.g. store.entries.js, you can follow the pattern you're familiar with, but with namespaced set to true:
const module = {
namespaced: true,
state: {},
getters: {},
mutations: {},
actions: {}
}
module.actions.update = ({ commit, state }, payload) => {
// commit, state, etc refer to the local store
}
export default module
For files using the store, you can import the index and then call by module:
import store from '../stores/store.index.js'
// ...
store.dispatch('entries/update', payload)
You can use state = Object.assign(state, payload). But note that this means you keep all existing props and shallow merge new ones in.
You cannot create a new state object unless you lift the logic up to the global namespace and use dynamic module registration