When there's a clear benefit to separating state management from components (e.g. due to state complexity) we recommend using [Vuex](https://vuex.vuejs.org) over any other Flux pattern. Otherwise, feel free to manage state within the components.
- You expect multiple parts of the application to react to state changes
- There's a need to share data between multiple components
- There are complex interactions with Backend, e.g. multiple API calls
- The app involves interacting with backend via both traditional REST API and GraphQL (especially when moving the REST API over to GraphQL is a pending backend task)
(For a more complex example implementation take a look at the security applications store in [here](https://gitlab.com/gitlab-org/gitlab/tree/master/ee/app/assets/javascripts/vue_shared/security_reports/store))
An action is usually composed by a `type` and a `payload` and they describe what happened. Unlike [mutations](#mutationsjs), actions can contain asynchronous operations - that's why we always need to handle asynchronous logic in actions.
#### Naming 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 mutation to toggle the loading state, we should:
1. A mutation with type `REQUEST_SOMETHING`, to toggle the loading state
1. A mutation with type `RECEIVE_SOMETHING_SUCCESS`, to handle the success callback
1. A mutation with type `RECEIVE_SOMETHING_ERROR`, to handle the error callback
1. An action `fetchSomething` to make the request and commit mutations on mentioned cases
1. In case your application does more than a `GET` request you can use these as examples:
-`POST`: `createSomething`
-`PUT`: `updateSomething`
-`DELETE`: `deleteSomething`
As a result, we can dispatch the `fetchNamespace` action from the component and it will be responsible to commit `REQUEST_NAMESPACE`, `RECEIVE_NAMESPACE_SUCCESS` and `RECEIVE_NAMESPACE_ERROR` mutations.
> Previously, we were dispatching actions from the `fetchNamespace` action instead of committing mutation, so please don't be confused if you find a different pattern in the older parts of the codebase. However, we encourage leveraging a new pattern whenever you write new Vuex stores
By following this pattern we guarantee:
1. All applications follow the same pattern, making it easier for anyone to maintain the code
1. All data in the application follows the same lifecycle pattern
Sometimes we may need to get derived state based on store state, like filtering for a specific prop.
Using a getter will also cache the result based on dependencies due to [how computed props work](https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods)
> 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.
1. Do not call a mutation directly. Always use an action to commit a mutation. Doing so will keep consistency throughout the application. From Vuex docs:
> Why don't we just call store.commit('action') directly? Well, remember that mutations must be synchronous? Actions aren't. We can perform asynchronous operations inside an action.
Because we're currently using [`babel-plugin-rewire`](https://github.com/speedskater/babel-plugin-rewire), you may encounter the following error when testing your Vuex actions and getters:
`[vuex] actions should be function or object with "handler" function`
To prevent this error from happening, you need to export an empty function as `default`:
When storing form data in Vuex, it is sometimes necessary to update the value stored. The store should never be mutated directly, and an action should be used instead.
In order to still use `v-model` in our code, we need to create computed properties in this form:
```javascript
export default {
computed: {
someValue: {
get() {
return this.$store.state.someValue;
},
set(value) {
this.$store.dispatch("setSomeValue", value);
}
}
}
};
```
An alternative is to use `mapState` and `mapActions`:
```javascript
export default {
computed: {
...mapState(['someValue']),
localSomeValue: {
get() {
return this.someValue;
},
set(value) {
this.setSomeValue(value)
}
}
},
methods: {
...mapActions(['setSomeValue'])
}
};
```
Adding a few of these properties becomes cumbersome, and makes the code more repetitive with more tests to write. To simplify this there is a helper in `~/vuex_shared/bindings.js`
The helper can be used like so:
```javascript
// this store is non-functional and only used to give context to the example
export default {
state: {
baz: '',
bar: '',
foo: ''
},
actions: {
updateBar() {...}
updateAll() {...}
},
getters: {
getFoo() {...}
}
}
```
```javascript
import { mapComputed } from '~/vuex_shared/bindings'
export default {
computed: {
/**
*@param {(string[]|Object[])} list - list of string matching state keys or list objects
*@param {string} list[].key - the key matching the key present in the vuex state
*@param {string} list[].getter - the name of the getter, leave it empty to not use a getter
*@param {string} list[].updateFn - the name of the action, leave it empty to use the default action
*@param {string} defaultUpdateFn - the default function to dispatch
*@param {string} root - optional key of the state where to search fo they keys described in list
*@returns {Object} a dictionary with all the computed properties generated
*/
...mapComputed(
[
'baz',
{ key: 'bar', updateFn: 'updateBar' }
{ key: 'foo', getter: 'getFoo' },
],
'updateAll',
),
}
}
```
`mapComputed` will then generate the appropriate computed properties that get the data from the store and dispatch the correct action when updated.