Part 6: State management with Vuex
Step 1: Setting up Vuex
First let's install the required dependency
npm i vuex
Create an empty store
// src/store/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const debug = process.env.NODE_ENV !== 'production' const store = new Vuex.Store({ strict: debug }) export default store
Rely the main Vue App with the store
// src/main.js import store from './store' // ... new Vue({ // ... store, })
Restart your application (npm run dev
) and open the DevTools > Vuex tab. The message No Vuex store detected. should disappear. You should now see an empty state.
Step 2: Define your first module
The very first step is to create an empty module dedicated to the hostel listing.
import * as types from '../mutation-types'
// initial state
const state = {
}
// getters
const getters = {
}
// actions
const actions = {
}
// mutations
const mutations = {
}
export default {
state,
getters,
actions,
mutations
}
also create an empty file mutation-types.js
. Then add this module in the global state:
const store = new Vuex.Store({
strict: debug,
modules: {
hostelList
}
})
Vuex: initial state
The first question you need to ask to yourself is: "Which data need to be shared across multiples components".
hostels
? Well, HostelList and HostelMap components only use hostelsShown. But hostelsShown is a computed property based on hostels. That's why we need it. For the same reasonuserSearch
,onlyShowLiked
andlikedHostels
are also needed.map
/markers
? No. Theses data are only for HostelMap component. As it stand, HostelList or whatever else component doesn't need this data.
const state = {
hostels: [],
userSearch: '',
onlyShowLiked: false,
likedHostels: []
}
Vuex: getters
The first obvious getter is hostelsShown
. Cut&Paste this function into the getter part.
We can also create an isLikedHostel()
getter for HostelListItem
component.
const getters = {
hostelsShown: (state) => {
const likeFilter = (hostel) => {
if (state.onlyShowLiked) {
return state.likedHostels.indexOf(hostel) !== -1
}
return true
}
return state.hostels
.filter(likeFilter)
.filter(hostel => hostel.name.toLowerCase().includes(state.userSearch.toLowerCase()))
},
isLikedHostel: (state) => (hostel) => {
return state.likedHostels.indexOf(hostel) !== -1
}
}
Vuex: mutations
First of all, we need to guess every operation that can change our components.
Once we got them, put everything in the mutation-type.js
.
It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allows the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application.
export const RECEIVE_HOSTELS = 'RECEIVE_HOSTELS'
export const TOGGLE_LIKE_HOSTEL = 'TOGGLE_LIKE_HOSTEL'
export const TEXT_FILTER_CHANGE = 'TEXT_FILTER_CHANGE'
export const LIKE_FILTER_CHANGE = 'LIKE_FILTER_CHANGE'
const mutations = {
[types.RECEIVE_HOSTELS] (state, { data }) {
state.hostels = data
},
[types.TOGGLE_LIKE_HOSTEL] (state, hostel) {
if (state.likedHostels.indexOf(hostel) === -1) {
state.likedHostels.push(hostel)
} else {
state.likedHostels.splice(state.likedHostels.indexOf(hostel), 1)
}
},
[types.LIKE_FILTER_CHANGE] (state) {
if (state.onlyShowLiked === false) {
state.onlyShowLiked = true
} else {
state.onlyShowLiked = false
}
},
[types.TEXT_FILTER_CHANGE] (state, newSearch) {
state.userSearch = newSearch
}
}
...what about load hostel method? This operation is asynchronous. That why, it should be on the actions part!
Vuex: actions
const actions = {
loadHostels ({ commit }) {
hostelApi.getAll().then(data => {
commit(types.RECEIVE_HOSTELS, { data })
})
}
}
Step 3: Map the store to the components
Now component should only focus on the view part. Let's map Vue components with the Vuex store using the mapState
, mapGetters
, mapMutations
and mapActions
methods.