summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/development/fe_guide/index.md5
-rw-r--r--doc/development/fe_guide/vuex.md131
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: