diff options
-rw-r--r-- | doc/development/fe_guide/index.md | 5 | ||||
-rw-r--r-- | doc/development/fe_guide/vuex.md | 131 |
2 files changed, 117 insertions, 19 deletions
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md index 2280cf79f86..73084d667d4 100644 --- a/doc/development/fe_guide/index.md +++ b/doc/development/fe_guide/index.md @@ -73,6 +73,11 @@ Vue specific design patterns and practices. --- +## [Vuex](vuex.md) +Vuex specific design patterns and practices. + +--- + ## [Axios](axios.md) Axios specific practices and gotchas. diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md index faa7e44f8bf..69f4588fafe 100644 --- a/doc/development/fe_guide/vuex.md +++ b/doc/development/fe_guide/vuex.md @@ -1,17 +1,16 @@ - -## Vuex +# Vuex To manage the state of an application you may use [Vuex][vuex-docs]. _Note:_ All of the below is explained in more detail in the official [Vuex documentation][vuex-docs]. -### Separation of concerns +## Separation of concerns Vuex is composed of State, Getters, Mutations, Actions and Modules. When a user clicks on an action, we need to `dispatch` it. This action will `commit` a mutation that will change the state. _Note:_ The action itself will not update the state, only a mutation should update the state. -#### File structure -When using Vuex at GitLab, separate this concerns into different files to improve readability. If you can, separate the Mutation Types as well: +## File structure +When using Vuex at GitLab, separate this concerns into different files to improve readability: ``` └── store @@ -19,11 +18,12 @@ When using Vuex at GitLab, separate this concerns into different files to improv ├── actions.js # actions ├── mutations.js # mutations ├── getters.js # getters + ├── state.js # getters └── mutation_types.js # mutation types ``` -The following examples show an application that lists and adds users to the state. +The following example shows an application that lists and adds users to the state. -##### `index.js` +### `index.js` This is the entry point for our store. You can use the following as a guide: ```javascript @@ -32,6 +32,7 @@ import Vuex from 'vuex'; import * as actions from './actions'; import * as getters from './getters'; import mutations from './mutations'; +import state from './state'; Vue.use(Vuex); @@ -39,19 +40,38 @@ export default new Vuex.Store({ actions, getters, mutations, - state: { - users: [], - }, + state, }); ``` -_Note:_ If the state of the application is too complex, an individual file for the state may be better. -#### `actions.js` +### `state.js` +The first thing you should do before writing any code is to design the state. + +Often we need to provide data from haml to our Vue application. Let's store it in the state for better access. + +```javascript + export default { + endpoint: null, + + isLoading: false, + error: null, + + isAddingUser: false, + errorAddingUser: false, + + users: [], + }; +``` + +#### Access `state` properties +You can use `mapState` to access state properties in the components. + +### `actions.js` An action is a playload of information to send data from our application to our store. They are the only source of information for the store. An action is usually composed by a `type` and a `payload` and they describe what happened. -By enforcing that every change is described as an action lets us have a clear understantid of what is going on in the app. +Enforcing that every change is described as an action lets us have a clear understanting of what is going on in the app. An action represents something that will trigger a state change, for example, when the user enters the page we need to load resources. @@ -84,10 +104,9 @@ In this file, we will write the actions (both sync and async) that will call the .then(({ data }) => dispatch('receiveAddUserSuccess', data)) .catch((error) => dispatch('receiveAddUserError', error)); } - ``` -##### Actions Pattern: `request` and `receive` namespaces +#### Actions Pattern: `request` and `receive` namespaces When a request is made we often want to show a loading state to the user. Instead of creating an action to toggle the loading state and dispatch it in the component, @@ -107,7 +126,7 @@ By following this patter we guarantee: 1. Unit tests are easier 1. Actions are simple and straightforward -##### Dispatching actions +#### Dispatching actions To dispatch an action from a component, use the `mapActions` helper: ```javascript import { mapActions } from 'vuex'; @@ -124,6 +143,9 @@ import { mapActions } from 'vuex'; }; ``` +#### Handling errors with `createFlash` +// TODO + #### `mutations.js` The mutations specify how the application state changes in response to actions sent to the store. The only way to actually change state in a Vuex store is by committing a mutation. @@ -138,14 +160,29 @@ Remember that actions only describe the fact that something happened, they don't import * as types from './mutation_types'; export default { - [types.ADD_USER](state, user) { + [types.REQUEST_USERS](state) { + Object.assign(state, { isLoading: true }); + }, + [types.RECEIVE_USERS_SUCCESS](state, data) { + // Do any needed data transformation to the received payload here + Object.assign(state, { users: data, isLoading: false }); + }, + [types.REQUEST_USERS_ERROR](state, error) { + Object.assign(state, { isLoading: false, error}); + }, + [types.REQUEST_ADD_USER](state, user) { + Object.assign(state, { isAddingUser: true }); + }, + [types.RECEIVE_ADD_USER_SUCCESS](state, user) { + Object.assign(state, { isAddingUser: false }); state.users.push(user); }, + [types.REQUEST_ADD_USER_ERROR](state, error) { + Object.assign(state, { isAddingUser: true , errorAddingUser: error}); + }, }; ``` - - #### `getters.js` Sometimes we may need to get derived state based on store state, like filtering for a specific prop. This can be done through the `getters`: @@ -191,6 +228,62 @@ The store should be included in the main component of your application: }; ``` +### Communicating with the Store +```javascript +<script> +import { mapActions, mapState, mapGetters } from 'vuex'; +import store from './store'; + +export default { + store, + computed: { + ...mapGetters([ + 'getUsersWithPets' + ]), + ...mapState([ + 'isLoading', + 'users', + 'error', + ]), + }, + methods: { + ...mapActions([ + 'fetchUsers', + 'addUser', + ]), + + onClickAddUser(data) { + this.addUser(data); + } + }, + + created() { + this.fetchUsers() + .catch(() => { + // TODO - Decide where to handle the `createFlash` + }) + } +} +</script> +<template> + <ul> + <li v-if="isLoading"> + Loading... + </li> + <li v-else-if="error"> + {{ error }} + </li> + <li + v-else + v-for="user in users" + :key="user.id" + > + {{ user }} + </li> + </ul> +</template> +``` + ### Vuex Gotchas 1. Do not call a mutation directly. Always use an action to commit a mutation. Doing so will keep consistency through out the application. From Vuex docs: |