If you are familiar with Vue, you may have heard of the state management library Vuex. It was previously recommended by Vue, until Pinia was developed by the team. However, many applications still use Vuex, including the one I am currently working on. This post provides some tips on how to handle state in a Vue app using Vuex. It assumes some familiarity with Vue and Vuex.
The Vuex Store
By using Vuex, we can manage the state of our application in a global store, which helps us avoid “prop drilling” – the process of passing props down through multiple levels of components. With Vuex, we can access the store and retrieve a part of our state to use in a variable, or mutate the state from within the component. Here is an example that uses Vue’s composition API:
“`html
{{ name }}
“`
Vuex allows us to use getters, actions, and mutations to handle state more efficiently. If `currentUser` is deeply nested within our store, it may be better to abstract it into a getter function in a separate JavaScript file, to avoid cluttering our component file with excessive logic. Here is an example:
“`html
const store = {
getters: {
getUsername: (state) => {
return store.state.currentUser.name
},
},
}
{{ name }}
“`
For larger applications, the Vuex store can become overwhelming. In the project I’m working on, a highly interactive area of the app required extensive state management, causing the Vuex store to become large and difficult to manage. We also encountered naming conflicts. In such cases, it is advisable to split the Vuex store into modules.
Creating Modules
As the name suggests, Vuex modules allow us to divide our store into smaller files, making them easier to manage and test. They also help us avoid naming conflicts by namespacing the module files. We don’t have to refactor the entire app to use modules. If a specific part of the state becomes unwieldy, we can separate it into its own module, while leaving the rest of the application code unchanged.
Previously, our store structure might have looked like this:
“`
store
index.js
actions.js
getters.js
mutations.js
“`
Let’s add a `modules` directory where we can store the getters, actions, and mutations associated with the data explorer section of our app:
“`
store
index.js
actions.js
getters.js
mutations.js
modules
dataExplorer
index.js
actions.js
getters.js
mutations.js
“`
We will create our new module in `store/modules/dataExplorer/index.js`. To keep things organized, I prefer to import the getters, actions, and mutations from their individual files, but you can write them in a single file if you prefer. We add `namespaced: true` to ensure our module is namespaced. By default, actions, mutations, and getters are all registered under the global namespace.
“`html
{{ name }}
“`
Registering a Module
We need to register the module with the store:
“`html
{{ name }}
“`
It is also possible to register a module dynamically:
“`html
store.registerModule(‘dataExplorer’, dataExplorer)
“`
Now our module will be available in the application state. We can access it in our component files, such as in a computed property:
“`html
{{ selectedDates }}
“`
Local and Global State
In our module, `state` refers to the module’s local state. For example, when writing a getter function, `state` here would be `state.dataExplorer` in the global state object:
“`html
const getters = {
getStartDate: (state) => state.startDate,
}
“`
We can use the namespaced getter function in our component file:
“`html
{{ selectedDates }}
“`
If we need to access the root state within a getter, we can use the third and fourth arguments of our getters:
“`html
const getters = {
getStartDate: (state) => state.startDate,
getGlobalStartDate: (state, getters, rootState, rootGetters) => {
return rootState.startDate
},
}
“`
Actions
Root getters can also be accessed by our actions:
“`html
const actions = {
updateStartDate: ({ dispatch, commit, rootGetters }) => {
const startDateGlobal = rootGetters.getStartDate
commit(‘updateDate’, startDateGlobal) // update local state with the date from the global state
},
}
“`
We can also dispatch root actions from our module:
“`html
const actions = {
updateStartDate: ({ dispatch, commit }, args) => {
dispatch(‘notifyUser’, args, { root: true }) // dispatch global action
},
}
“`
To dispatch a local action from a component, we use it like this:
“`html
{{ selectedDates }}
“`
Getting Organized
Modules are a useful way to manage state in large applications. It is even possible to nest modules and choose whether they inherit the parent namespace. Modules can be implemented incrementally, avoiding the need for a complete application-wide refactor. So, if your global state is becoming unmanageable, it might be worth trying out modules today.
Source link