I have a vue.js/nuxt.js component in my UI that displays news based on a backend which can be queried with selectors (e.g. news-type1, news-type2).
I want to add a second instance of that component which uses exactly the same backend, but allows the user to use a few different selectors (e.g. news-type3, news-type4). The UI kinda works dashboard-like. Implementing that distinction in the .vue component file is no problem (just accept some props and display stuff conditionally to the user), but:
How do I reuse the vuex store? The code of the store for the new card stays exactly the same since the same backend is used. But I can't use the same instance of the store because the selectors and the loaded news should be stored per component and should not be shared between them. Surprisingly I haven't been able to find any easy solutions for that in nuxt. I thought this would be a common use case.
MWE for my use case:
/** Vuex store in store/news.js **/
export const state = () => ({
// per default only news-type1 is selected, but not news-type2. the user can change that in the UI
currentSelectors: ['news-type1'],
news = [] // object array containing the fetched news
});
export const mutations = {
// some very simple mutations for the stae
setSelectors (state, data) {
state.currentSelectors = data;
},
setNews (state, data) {
state.news = data;
}
}
export const actions = {
// simplified get and commit function based on the currentSelectors
async loadNews ({ commit, state }) {
const news = await this.$axios.$get(`/api/news/${state.currentSelectors.join(',')}`);
commit('setNews', news);
// ... truncated error handling
},
// Helper action. In comparison to the mutation with the same name, it also calls the load action
async setSelectors ({ commit, dispatch }, selectors) {
commit('setSelectors', selectors);
dispatch('loadNews');
},
};
In my news-card.vue I simply map the two states and call the two actions loadNews (initial load) and setSelectors (after user changes what news to show in the UI). This should stay the same in both instances of the card, it just should go to different store instances.
My current alternative would be to simply copy-paste the store code to a new file store/news-two.js and then using either that store or the original store depending on which prop is passed to my news-card component. For obvious reasons, that would be bad practice. Is there a better complicated alternative that works with nuxt?
All related questions I have found are only for Vue, not for nuxt vuex stores: Need multiple instances of Vuex module for multiple Vue instances or How to re-use component that should use unique vuex store instance.
I have problem understanding why THE SAME mutation fails to refresh data displayed in components (although it does change underlying vuex store data!) if it is called from one of the components, but it does refresh the data if called from another component?
I am updating Filter objects stored in store this way: state.report.filters[], where filters is array of Filter objects.
const state = {
report: {
filters: [], // array of Filter objects
...
}
}
My mutation looks for a filter in the array and substitutes the whole Filter object.
const mutations = {
setFilter: (state, newFilterValue) => {
let changedFilter = state.report.filters.find(filter => {
return filter.fieldName === newFilterValue.fieldName;
});
changedFilter = newFilterValue;
}
}
The mutation is called from a method of Filter class defined like this (separate module):
import { store } from './store';
export class Filter {
constructor ({
...
} = {}) {
this.operators = []; // Array of Operator objects
this.value = []; // Array of values - in this case Dates
};
updateOperator (operatorName) { // this mutation refreshes components when executed
this.operator[0] = new Operator(operatorName);
store.commit('setFilter', this); // whole object passed to the mutation
};
updateValue (newValue) { // this mutation changes store value, but fails to refresh components
this.value[0] = newValue; // newValue is a Date
store.commit('setFilter', this);
};
};
The app displays data in rows (each Filter has a separate row), each row contains cells, of which one contains components dedicated to Filter's value and Operator. These dedicated components receive as props callback functions which are methods of the Filter object. They execute the callback functions when a new value is entered passing the value to the Filter which then updates a relevant property and calls the mutation passing in both cases the whole Filter object as payload.
// TABLE CELL COMPONENT displaying filter value and operator
<template>
<td>
<operator-component
:iconName="proppedFilterObject.operator.iconName"
:callback="proppedFilterObject.updateOperator.bind(proppedFilterObject)"
></operator-component>
<value-component
:date="proppedFilterObject.value[0]"
:callback="proppedFilterObject.updateValue.bind(proppedFilterObject)"
></value-component>
</td>
</template>
<script>
export default {
props: ['proppedFilterObject'] // whole filter object
};
</script>
// OPERATOR COMPONENT
<template>
<div #click.stop="chooseOperator">
{{ iconName }} // some operator value display
</div>
</template>
<script>
export default {
methods: {
chooseOperator () {
const modal = new ChooseOperatorModal({
callback: this.callback // this displays another modal for receiving data. The modal calls the callback.
});
},
},
props: ['callback', 'iconName']
};
</script>
// VALUE COMPONENT
<template>
<date-picker v-model="computedDate"> // THIRD PARTY COMPONENT
</date-picker>
{{ date }} // additional display to verify if there's a problem within 'date-picker'
</template>
<script>
import DatePicker from 'vue2-datepicker'; // THIRD PARTY COMPONENT
export default {
components: { DatePicker },
computed: {
computedDate: {
get: function () {
return this.date;
},
set: function (newValue) {
this.callback(newValue);
}
}
},
props: ['callback', 'date']
};
</script>
So, if eg. I enter new operator value from Operator component, everything refreshes. When I enter a new value in the value component, the mutation is executed and store value changed, but displayed data are not refreshed. However, if afterwards I change an operator all the components will refresh and value will get displayed. Even if I change operator in a different Filter object(!). Ie:
a) Change in report.filters[0].value - display not refreshed, but...
b) then change report.filters[1].operator - both report.filters[1].operator AND PREVIOUSLY CHANGED report.filters[0].value get refreshed(?!).
What can be a reason of such behaviour? Where to look for the problem?
Some additional remarks:
1) I am using a third party component "vue2-date-picker" for date choice and display. However it does not seem to be responsible for the problem, as if I try to display the new value just in {{ }} notation it behaves the same way. I have used the date picker in other components and there it functions correctly as well.
2) In the code samples I left out most imports/exports and other seemingly irrelevant elements to keep the question reasonably short.
There are a lot of problems with the code and several of them are contributing to the problems you're seeing. A full, thorough answer that addresses all of these problems would be ridiculously long so instead I will skim through them without going into huge amounts of detail. You will need to do some further reading and experimentation to understand each of these topics properly.
Let's start with this line in the mutation:
changedFilter = newFilterValue;
This line assigns a new value to the local variable changedFilter. That's all. As it's the last line of the mutation the net result is that it doesn't really do anything.
Presumably your intent was to update the array state.report.filters, replacing the old entry with a new entry. However, just updating a local variable isn't going to do that.
At this point you may be wondering 'If that doesn't do anything, then why is the state in my store changing?'. I'll come to that in a moment but first let me prove to you that your existing code does nothing.
Try removing the code inside setFilter completely. Just leave an empty function. Then try clicking around in the UI just like you did before. You'll find that the store state updates just the same as it did before, even though you've removed the code to update the array.
The correct way to implement that mutation would be to use findIndex to find the relevant index and then use either Vue.set or the array's splice method to update the array accordingly. That will change the item in the array. However...
This brings us back to the earlier question. Why is the state updating if the mutation does nothing?
This is because you're using the same object in multiple places. The Filter object held in the array is the same object that your UI is editing. There are no copies being taken, there is just a single object. So when you change the properties of that object inside updateOperator or updateValue this will immediately be reflected inside the store. Calling the setFilter mutation is just asking the store to replace an object with itself.
There's nothing specific to Vue about this. This is just the standard behaviour of reference types in JavaScript. It is also common with many other programming languages that don't directly expose pointers. It can be useful to learn a little about how pointers work in other languages as it will give you a better initial mental model before attempting to understand how reference types behave in JavaScript. Understanding the difference between 'by value' and 'by reference' may also be a useful starting point.
The next topic to cover is reactivity, which very much is a Vue topic.
Specifically, there are certain changes that Vue can't detect. These are usually referred to as the reactivity caveats. You can find more about them in the official documentation:
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
https://v2.vuejs.org/v2/guide/list.html#Caveats
There are at least two lines in your code that violate these rules:
this.operator[0] = new Operator(operatorName);
and
this.value[0] = newValue;
You can't set array entries directly by index. The array will update but it won't trigger any reactive dependencies within Vue. Instead you need to use either Vue.set or one of the array methods, e.g. push, pop, splice, etc.. In this example you could use splice.
e.g. Using Vue.set:
Vue.set(this.value, 0, newValue);
e.g. Using splice:
this.value.splice(0, 0, newValue);
Why does all of this matters?
Well Vue will only re-render a component if its reactive dependencies have changed. They are very similar to computed properties in that regard. Here's how it works...
Vue compiles the template down to a function. That function is referred to as the render function. When rendering a component Vue calls the render function and that function returns a description of how to render the component. Any reactive properties that are touched while that function is running will be recorded as dependencies. If, at some point in the future, the value of one of those reactive properties changes then Vue will rerun the render function to generate a new rendering of that component.
There are two key points to take out of this description:
If you fall foul of one of the reactivity caveats then Vue won't know the dependency has changed, so it won't re-render the component.
The render function runs as a whole. It doesn't just target a small chunk of the template, it always runs the whole thing.
So if you change a dependency in a non-reactive way (i.e. one of the caveats) it won't trigger a rendering update. But if you subsequently update a dependency properly, Vue will detect that and will rerun the render function. When it runs it will run the whole thing, so any new values will be picked up, even if they weren't detected when they changed.
It isn't immediately clear to me which rendering dependency is causing your component to re-render. However, it only needs one of them to change in a detectable manner. Any other changes will then get pulled in incidentally when the render function runs and reads their current values.
That covers why your code isn't working. However, I would also worry about your decision to introduce a Filter class. I understand how that may be appealing if you've come from some other OO environment but it isn't typically how Vue is used. It is possible to make it work but you will need a good understanding of both JavaScript reference types and the Vue reactivity system to avoid falling through the cracks. There is no reason why using a specific class to hold your data can't be made to work but in practice it usually ends up being less maintainable than not using such a class. A more typical Vue approach would be to use simple, anonymous objects/arrays to hold the data and then for the data owner (either a component or store module) to be responsible for making any mutations to that data. Events are used to pass changes up the component hierarchy rather than callback props.
Ultimately you will need to judge whether the Filter class is justified but it is probably not what future maintainers of your code will be expecting.
EDIT AGAIN: Opened an issue with Reflux here: https://github.com/reflux/refluxjs/issues/544
EDIT: Reflux setState does not provide any callback for setState. They require you to use the component lifecycle methods to ensure the state is set prior to running any code. If you ever need to use the reflux setState outside of a component, where you do not have lifecycle methods, you will not be guaranteed the state is set. This is due to how Reflux does their setState. It loops all listening components and calls those components' setState methods. If Reflux were refactored to wait until all the listening components' setState calls complete then call a callback passed into its own setState method, that may work, but it would likely require a large rework of Reflux. I have started using a singleton class to manage some of these variables, as they are fully outside the component lifecycle.
Can you use setState with a callback in ReactNative or is that only in React? I'm using the below syntax and the first debugger is hit, but the second debugger and console log never get hit.
EDIT: After digging some more, it seems this does not occur when using setting the state directly, but only when running it through a reflux store and/or not using a component.
See snack here: https://snack.expo.io/S1dm3eFoM
debugger
this.setState(
params,
() => {
debugger
console.log("CALLIN IT BACK")
}
)
I'm the creator of Reflux's ES6 styled stores/component hookups. Hopefully I can shed some light on this for you.
Here's the important points:
1) Reflux sets its store state immediately upon setState calls.
Reflux's store state doesn't have the same problems as React and doesn't need React's workaround (callback). You are guaranteed that your change is immediately reflected in the store's state, that's why there is not a callback. The very next line of code will reflect the store's new state.
tl;dr, no workaround is required.
// in Reflux stores this works
this.setState({foo:'foo'});
console.log(this.state.foo === 'foo') // true
this.setState({foo:'bar'});
console.log(this.state.foo === 'bar') // true
2) Stores can never depend upon components!
The idea that the setState would give a callback about when the dependent components have all updated their state is a major violation of the single most fundamental of all flux principles: 1 way data flow.
If your store requires knowledge about whether or not components are doing something then you are already doing it wrong, and all the problems you are experiencing are XY problems of fundamentally not following flux in the first place. 1-way data flow is a main flux principle.
And that principle exists for good reason. Flux doesn't require 1:1 mapping of store state properties to component state properties. You can map anything to anything, or even just use the store's state for the building blocks of how you will run your own logic to create completely new state properties on the components. For example having loaded and transitioned as separate properties in store state, but mapping to a loadedAndTransitioned property in one component, and a notLoadedOrTransitioned in another component via your own custom logic. That's a hugely powerful part of flux. But your suggestion would pretty much destroy all that, since Reflux can't map people's custom logic.
1-way data flow must be maintained; Store's must operate the same independently of what components utilize them. Without this, the power of flux falls apart!
Store's listen to actions, components listen to stores, actions are called from wherever. All flux-based data flows from action -> store -> component only.
I've checked the library for the refluxjs and the problem and the workaround are as mentioned below.
Problem
The library provides with a new instance of the setState which is not exactly similar to ReactJS setState, which omits the callback as mentioned in their code below.
/dist/reflux.js
proto.setState = function (obj) {
// Object.assign(this.state, obj); // later turn this to Object.assign and remove loop once support is good enough
for (var key in obj) {
this.state[key] = obj[key];
}
// if there's an id (i.e. it's being tracked by the global state) then make sure to update the global state
if (this.id) {
Reflux.GlobalState[this.id] = this.state;
}
// trigger, because any component it's attached to is listening and will merge the store state into its own on a store trigger
this.trigger(obj);
};
Also as mentioned here in the docs
That store will store its state on a this.state property, and mutate its state via this.setState() in a way that is extremely similar to React classes themselves.
WorkAround
The library provides with the listener functions, which provide us with the callbacks of the setState obj of the ReactJS as mentioned in the below snippet.
/dist/reflux.js
componentDidMount: function() {
var me = this;
_.extend(me, ListenerMethods);
this.listenTo(listenable, function(v) {
me.setState(_.object([key],[v]));
});
},
You can use them in the following way
this.listenTo(action, callback)
Hope it clears the doubts
Edit:
Usage as per the docs
To listen inside of the store
constructor()
{
super();
this.state = {count: 0};
this.listenTo(increment, this.incrementItUp);
}
incrementItUp()
{
var newCount = this.state.count + 1;
this.setState({count: newCount});
}
To listen outside of the store anywhere
// listen directly to an action
myActions.actionName.listen(myCallbackFunc);
// listen to a child action
myActions.load.completed.listen(myCallbackFunc);
Here's the link to the snack with working callbacks based on Promises
In Vuex, what is the logic of having both "actions" and "mutations?"
I understand the logic of components not being able to modify state (which seems smart), but having both actions and mutations seems like you are writing one function to trigger another function, to then alter state.
What is the difference between "actions" and "mutations," how do they work together, and moreso, I'm curious why the Vuex developers decided to do it this way?
Question 1: Why did the Vuejs developers decide to do it this way?
Answer:
When your application becomes large, and when there are multiple developers working on this project, you will find that "state management" (especially the "global state") becomes increasingly more complicated.
The Vuex way (just like Redux in react.js) offers a new mechanism to manage state, keep state, and "save and trackable" (that means every action which modifies state can be tracked by debug tool:vue-devtools)
Question 2: What's the difference between "action" and "mutation"?
Let's see the official explanation first:
Mutations:
Vuex mutations are essentially events: each mutation has a name and a
handler.
import Vuex from 'vuex'
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
INCREMENT (state) {
// mutate state
state.count++
}
}
})
Actions: Actions are just functions that dispatch mutations.
// the simplest action
function increment ({commit}) {
commit('INCREMENT')
}
// a action with additional arguments
// with ES2015 argument destructuring
function incrementBy ({ dispatch }, amount) {
dispatch('INCREMENT', amount)
}
Here is my explanation of the above:
A mutation is the only way to modify state
The mutation doesn't care about business logic, it just cares about "state"
An action is business logic
The action can commit more than 1 mutation at a time, it just implements the business logic, it doesn't care about data changing (which is managed by mutation)
Mutations are synchronous, whereas actions can be asynchronous.
To put it in another way: you don't need actions if your operations are synchronous, otherwise implement them.
I believe that having an understanding of the motivations behind Mutations and Actions allows one to better judge when to use which and how. It also frees the programmer from the burden of uncertainty in situations where the "rules" become fuzzy. After reasoning a bit about their respective purposes, I came to the conclusion that although there may definitely be wrong ways to use Actions and Mutations, I don't think that there's a canonical approach.
Let's first try to understand why we even go through either Mutations or Actions.
Why go through the boilerplate in the first place? Why not change state directly in components?
Strictly speaking you could change the state directly from your components. The state is just a JavaScript object and there's nothing magical that will revert changes that you make to it.
// Yes, you can!
this.$store.state['products'].push(product)
However, by doing this you're scattering your state mutations all over the place. You lose the ability to simply just open a single module housing the state and at a glance see what kind of operations can be applied to it. Having centralized mutations solves this, albeit at the cost of some boilerplate.
// so we go from this
this.$store.state['products'].push(product)
// to this
this.$store.commit('addProduct', {product})
...
// and in store
addProduct(state, {product}){
state.products.push(product)
}
...
I think if you replace something short with boilerplate you'll want the boilerplate to also be small. I therefore presume that mutations are meant to be very thin wrappers around native operations on the state, with almost no business logic. In other words, mutations are meant to be mostly used like setters.
Now that you've centralized your mutations you have a better overview of your state changes and since your tooling (vue-devtools) is also aware of that location it makes debugging easier. It's also worth keeping in mind that many Vuex's plugins don't watch the state directly to track changes, they rather rely on mutations for that. "Out of bound" changes to the state are thus invisible to them.
So mutations, actions what's the difference anyway?
Actions, like mutations, also reside in the store's module and can receive the state object. Which implies that they could also mutate it directly. So what's the point of having both? If we reason that mutations have to be kept small and simple, it implies that we need an alternative means to house more elaborate business logic. Actions are the means to do this. And since as we have established earlier, vue-devtools and plugins are aware of changes through Mutations, to stay consistent we should keep using Mutations from our actions. Furthermore, since actions are meant to be all encompassing and that the logic they encapsulate may be asynchronous, it makes sense that Actions would also simply made asynchronous from the start.
It's often emphasized that actions can be asynchronous, whereas mutations are typically not. You may decide to see the distinction as an indication that mutations should be used for anything synchronous (and actions for anything asynchronous); however, you'd run into some difficulties if for instance you needed to commit more than one mutations (synchronously), or if you needed to work with a Getter from your mutations, as mutation functions receive neither Getters nor Mutations as arguments...
...which leads to an interesting question.
Why don't Mutations receive Getters?
I haven't found a satisfactory answer to this question, yet. I have seen some explanation by the core team that I found moot at best. If I summarize their usage, Getters are meant to be computed (and often cached) extensions to the state. In other words, they're basically still the state, albeit that requires some upfront computation and they're normally read-only. That's at least how they're encouraged to be used.
Thus, preventing Mutations from directly accessing Getters means that one of three things is now necessary, if we need to access from the former some functionality offered by the latter: (1) either the state computations provided by the Getter is duplicated somewhere that is accessible to the Mutation (bad smell), or (2) the computed value (or the relevant Getter itself) is passed down as an explicit argument to the Mutation (funky), or (3) the Getter's logic itself is duplicated directly within the Mutation, without the added benefit of caching as provided by the Getter (stench).
The following is an example of (2), which in most scenarios that I have encountered seems the "least bad" option.
state:{
shoppingCart: {
products: []
}
},
getters:{
hasProduct(state){
return function(product) { ... }
}
}
actions: {
addProduct({state, getters, commit, dispatch}, {product}){
// all kinds of business logic goes here
// then pull out some computed state
const hasProduct = getters.hasProduct(product)
// and pass it to the mutation
commit('addProduct', {product, hasProduct})
}
}
mutations: {
addProduct(state, {product, hasProduct}){
if (hasProduct){
// mutate the state one way
} else {
// mutate the state another way
}
}
}
To me, the above seems not only a bit convoluted, but also somewhat "leaky", since some of the code present in the Action is clearly oozing from the Mutation's internal logic.
In my opinion, this is an indication of a compromise. I believe that allowing Mutations to automatically receive Getters presents some challenges. It can be either to the design of Vuex itself, or the tooling (vue-devtools et al), or to maintain some backward compatibility, or some combination of all the stated possibilities.
What I don't believe is that passing Getters to your Mutations yourself is necessarily a sign that you're doing something wrong. I see it as simply "patching" one of the framework's shortcomings.
The main differences between Actions and Mutations:
In mutations you can change the state but not it actions.
Inside actions you can run asynchronous code but not in mutations.
Inside actions you can access getters, state, mutations (committing them), actions (dispatching them) etc in mutations you can access only the state.
I think the TLDR answer is that Mutations are meant to be synchronous/transactional. So if you need to run an Ajax call, or do any other asynchronous code, you need to do that in an Action, and then commit a mutation after, to set the new state.
I have been using Vuex professionally for about 3 years, and here is what I think I have figured out about the essential differences between actions and mutations, how you can benefit from using them well together, and how you can make your life harder if you don't use it well.
The main goal of Vuex is to offer a new pattern to control the behaviour of your application: Reactivity. The idea is to offload the orchestration of the state of your application to a specialized object: a store. It conveniently supplies methods to connect your components directly to your store data to be used at their own convenience. This allows your components to focus on their job: defining a template, style, and basic component behaviour to present to your user. Meanwhile, the store handles the heavy data load.
That is not the only advantage of this pattern though. The fact that stores are a single source of data for the entirety of your application offers a great potential for re-usability of this data across many components. This isn't the first pattern that attempts to address this issue of cross-component communication, but where it shines is that it forces you to implement a very safe behaviour in your application by basically forbidding your components to modify the state of this shared data, and force it instead to use "public endpoints" to ask for change.
The basic idea is this:
The store has an internal state, which should never be directly accessed by components (mapState is effectively banned)
The store has mutations, which are synchronous modifications to the internal state. A mutation's only job is to modify the state. They should only be called from an action. They should be named to describe things that happened to the state (ORDER_CANCELED, ORDER_CREATED). Keep them short and sweet. You can step through them by using the Vue Devtools browser extension (it's great for debugging too!)
The store also has actions, which should be async or return a promise. They are the actions that your components will call when they want to modify the state of the application. They should be named with business oriented actions (verbs, i.e. cancelOrder, createOrder). This is where you validate and send your requests. Each action may call different commits at different steps if it is required to change the state.
Finally, the store has getters, which are what you use to expose your state to your components. Expect them to be heavily used across many components as your application expands. Vuex caches getters heavily to avoid useless computation cycles (as long as you don't add parameters to your getter - try not to use parameters) so don't hesitate to use them extensively. Just make sure you give names that describe as closely as possible what state the application currently is in.
That being said, the magic begins when we start designing our application in this manner. For example:
We have a component that offers a list of orders to the user with the possibility to delete those orders
The component has mapped a store getter (deletableOrders), which is an array of objects with ids
The component has a button on each row of orders, and its click is mapped to a store action (deleteOrder) which passes the order object to it (which, we will remember, comes from the store's list itself)
The store deleteOrder action does the following:
it validates the deletion
it stores the order to delete temporarily
it commits the ORDER_DELETED mutation with the order
it sends the API call to actually delete the order (yes, AFTER modifying the state!)
it waits for the call to end (the state is already updated) and on failure, we call the ORDER_DELETE_FAILED mutation with the order we kept earlier.
The ORDER_DELETED mutation will simply remove the given order from the list of deletable orders (which will update the getter)
The ORDER_DELETE_FAILED mutation simply puts it back, and modifies the state to notify of the error (another component, error-notification, would be tracking that state to know when to display itself)
In the end, we have a user experience that is deemed as "reactive". From the perspective of our user, the item has been deleted immediately. Most of the time, we expect our endpoints to just work, so this is perfect. When it fails, we still have some control over how our application will react, because we have successfully separated the concern of the state of our front-end application, with the actual data.
You don't always need a store, mind you. If you find that you are writing stores that look like this:
export default {
state: {
orders: []
},
mutations: {
ADD_ORDER (state, order) {
state.orders.push(order)
},
DELETE_ORDER (state, orderToDelete) {
state.orders = state.orders.filter(order => order.id !== orderToDelete.id)
}
},
actions: {
addOrder ({commit}, order) {
commit('ADD_ORDER', order)
},
deleteOrder ({commit}, order) {
commit('DELETE_ORDER', order)
}
},
getters: {
orders: state => state.orders
}
}
To me it seems you are only using the store as a data store, and are perhaps missing out on the reactivity aspect of it, by not letting it also take control of variables that your application reacts to. Basically, you can and should probably offload some lines of code written in your components to your stores.
According to the docs
Actions are similar to mutations, the differences being that:
Instead of mutating the state, actions commit mutations.
Actions can contain arbitrary asynchronous operations.
Consider the following snippet.
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++ //Mutating the state. Must be synchronous
}
},
actions: {
increment (context) {
context.commit('increment') //Committing the mutations. Can be asynchronous.
}
}
})
Action handlers(increment) receive a context object which exposes the same set of
methods/properties on the store instance, so you can call
context.commit to commit a mutation, or access the state and getters
via context.state and context.getters
Mutations:
Can update the state. (Having the Authorization to change the state).
Actions:
Actions are used to tell "which mutation should be triggered"
In Redux Way
Mutations are Reducers
Actions are Actions
Why Both ??
When the application growing , coding and lines will be increasing , That time you have to handle the logic in Actions not in the mutations because mutations are the only authority to change the state, it should be clean as possible.
Disclaimer - I've only just started using vuejs so this is just me extrapolating the design intent.
Time machine debugging uses snapshots of the state, and shows a timeline of actions and mutations. In theory we could have had just actions alongside a recording of state setters and getters to synchronously describe mutation. But then:
We would have impure inputs (async results) which caused the setters and getters. This would be hard to follow logically and different async setters and getters may surprisingly interact. That can still happen with mutations transactions but then we can say the transaction needs to be improved as opposed to it being a race condition in the actions. Anonymous mutations inside an action could more easily resurface these kinds of bugs because async programming is fragile and difficult.
The transaction log would be hard to read because there would be no name for the state changes. It would be much more code-like and less English, missing the logical groupings of mutations.
It might be trickier and less performant to instrument recording any mutation on a data object, as opposed to now where there are synchronously defined diff points - before and after mutation function call. I'm not sure how big of a problem that is.
Compare the following transaction log with named mutations.
Action: FetchNewsStories
Mutation: SetFetchingNewsStories
Action: FetchNewsStories [continuation]
Mutation: DoneFetchingNewsStories([...])
With a transaction log that has no named mutations:
Action: FetchNewsStories
Mutation: state.isFetching = true;
Action: FetchNewsStories [continuation]
Mutation: state.isFetching = false;
Mutation: state.listOfStories = [...]
I hope you can extrapolate from that example the potential added complexity in async and anonymous mutation inside actions.
https://vuex.vuejs.org/en/mutations.html
Now imagine we are debugging the app and looking at the devtool's mutation logs. For every mutation logged, the devtool will need to capture a "before" and "after" snapshots of the state. However, the asynchronous callback inside the example mutation above makes that impossible: the callback is not called yet when the mutation is committed, and there's no way for the devtool to know when the callback will actually be called - any state mutation performed in the callback is essentially un-trackable!
This confused me too so I made a simple demo.
component.vue
<template>
<div id="app">
<h6>Logging with Action vs Mutation</h6>
<p>{{count}}</p>
<p>
<button #click="mutateCountWithAsyncDelay()">Mutate Count directly with delay</button>
</p>
<p>
<button #click="updateCountViaAsyncAction()">Update Count via action, but with delay</button>
</p>
<p>Note that when the mutation handles the asynchronous action, the "log" in console is broken.</p>
<p>When mutations are separated to only update data while the action handles the asynchronous business
logic, the log works the log works</p>
</div>
</template>
<script>
export default {
name: 'app',
methods: {
//WRONG
mutateCountWithAsyncDelay(){
this.$store.commit('mutateCountWithAsyncDelay');
},
//RIGHT
updateCountViaAsyncAction(){
this.$store.dispatch('updateCountAsync')
}
},
computed: {
count: function(){
return this.$store.state.count;
},
}
}
</script>
store.js
import 'es6-promise/auto'
import Vuex from 'vuex'
import Vue from 'vue';
Vue.use(Vuex);
const myStore = new Vuex.Store({
state: {
count: 0,
},
mutations: {
//The WRONG way
mutateCountWithAsyncDelay (state) {
var log1;
var log2;
//Capture Before Value
log1 = state.count;
//Simulate delay from a fetch or something
setTimeout(() => {
state.count++
}, 1000);
//Capture After Value
log2 = state.count;
//Async in mutation screws up the log
console.log(`Starting Count: ${log1}`); //NRHG
console.log(`Ending Count: ${log2}`); //NRHG
},
//The RIGHT way
mutateCount (state) {
var log1;
var log2;
//Capture Before Value
log1 = state.count;
//Mutation does nothing but update data
state.count++;
//Capture After Value
log2 = state.count;
//Changes logged correctly
console.log(`Starting Count: ${log1}`); //NRHG
console.log(`Ending Count: ${log2}`); //NRHG
}
},
actions: {
//This action performs its async work then commits the RIGHT mutation
updateCountAsync(context){
setTimeout(() => {
context.commit('mutateCount');
}, 1000);
}
},
});
export default myStore;
After researching this, the conclusion I came to is that mutations are a convention focused only on changing data to better separate concerns and improve logging before and after the updated data. Whereas actions are a layer of abstraction that handles the higher level logic and then calls the mutations appropriately
Because there’s no state without mutations! When commited — a piece of logic, that changes the state in a foreseeable manner, is executed. Mutations are the only way to set or change the state (so there’s no direct changes!), and furthermore — they must be synchronous. This solution drives a very important functionality: mutations are logging into devtools. And that provides you with a great readability and predictability!
One more thing — actions. As it’s been said — actions commit mutations. So they do not change the store, and there’s no need for these to be synchronous. But, they can manage an extra piece of asynchronous logic!
It might seem unnecessary to have an extra layer of actions just to call the mutations, for example:
const actions = {
logout: ({ commit }) => {
commit("setToken", null);
}
};
const mutations = {
setToken: (state, token) => {
state.token = token;
}
};
So if calling actions calls logout, why not call the mutation itself?
The entire idea of an action is to call multiple mutations from inside one action or make an Ajax request or any kind of asynchronous logic you can imagine.
We might eventually have actions that make multiple network requests and eventually call many different mutations.
So we try to stuff as much complexity from our Vuex.Store() as possible in our actions and this leaves our mutations, state and getters cleaner and straightforward and falls in line with the kind of modularity that makes libraries like Vue and React popular.
1.From docs:
Actions are similar to mutations, the differences being that:
Instead of mutating the state, actions commit mutations.
Actions can contain arbitrary asynchronous operations.
The Actions can contain asynchronous operations, but the mutation can not.
2.We invoke the mutation, we can change the state directly. and we also can in the action to change states by like this:
actions: {
increment (store) {
// do whatever ... then change the state
store.commit('MUTATION_NAME')
}
}
the Actions is designed for handle more other things, we can do many things in there(we can use asynchronous operations) then change state by dispatch mutation there.