For example, I use a Getter for write a logic about a if object has a especify content.
const state = {
object = []
}
const getters = {
CHECK_OBJECT_STATE: (state) => {
if(Object.prototype.hasOwnProperty.call(state.object, "error")){
//return a message, in this case is not necessary proceed the logic.
}else{
//otherwise I need take state.object.id and fetch a new a action.
//CALL ACTION FETCH_OBJECT_ID
}
}
}
const actions = {
FETCH_OBJECT(...){
//SET MUTATION HERE
}
FETCH_OBJECT_ID (..) {..}
}
anyone can suggest me other method to do it, if it's not valid?
Vuex Getters are not designed for asynchronous tasks, those should be handled in the actions. I really think that getters operate best as pure functions which perform read-only operations on an existing state, so it should do only one job: retrieve the data in the state.
So you process the logic, the if statement in the actions, store the returned value in a state array object (which is pretty weird for me to name an array "object"), and the getter only has to retrieve such array. Things are reactive so that getter will always hold the latest data set.
Related
Except that a mutation must be synchronous whereas an action can contain asynchronous code, I find it difficult to establish some guidelines on how to correctly choose where the logic is supposed to be implemented.
The Vuex doc describes the mutations as transactions, which makes me think that each mutation must ensure as much as possible state coherence, potentially doing multiple changes in the store within the same mutation.
Let’s take for example the following state, storing documents that can either be loading or loaded:
const state = {
loading: []
loaded: []
}
const mutations = {
setLoaded(state, doc) {
state.loaded.push(doc)
// Remove doc from loading:
const index = state.loading.find(loadingDoc => doc.id === loadingDoc.id)
if (index !== -1) {
state.loading.splice(index, 1)
}
}
}
Here the mutation ensures that the doc is either in loading or loaded state, not both. The problem doing so is that the mutations could eventually end up having a lot of logic, which could make them more difficult to debug through the Vuex dev tools.
Some prefer to keep the mutations as simple as possible (eg. only one state change per mutation), but then it’s up to the actions (or the components in case they directly call mutations) to ensure state coherence, which could be more error-prone since, in the above example, you would need to remove the doc from loading state each time you call the “setLoaded” mutation.
As for data validation, unluckily we have no simple way to do it in mutations since mutations cannot return anything. So an action calling a mutation has no way to know that the data is invalid. Eg.:
const mutations = {
setValue(state, intValue) {
if (intValue < 0) {
// error
return
}
state.value = intValue;
}
}
Thus, it seems that data validation needs to be done in actions, with mutations assuming that the data they receive are correct.
What are your thoughts about this, and more generally about actions and mutations? Do you have guidelines on how to properly use them from your own experience?
Data validation MUST be done in actions if it encompasses multiple mutations and/or needs to signal to the caller if the operation failed.
My rule (and this will be argued...) is to always use async CALLS (with async/await) whenever possible, even if I am calling sync methods. Then my code is future proof if the sync method becomes async. And in the case you mentioned, it also allows a return value and/or error from Vuex.
let response
response = await anAsyncMethod()
response = await aSyncMethod()
response = await aVuexMutateOp()
response = await aVueActionOp()
Yes this adds a bit of processing overhead, but it is also just as clear by using async/await syntax. It streamlines the error processing as well, routing it to a catch block.
Because mutations have the restriction that they MUST be synchronous AND CANNOT call other mutations AND DO NOT return a value demonstrates that the mutate should only be used in the simplest of situations.
I am trying to use Vuex and am having trouble to understand why my data is not the expected value.
What I am trying to do is pass the result of a getter to a mutation commit as follows
// TEST PRINT: return value of getter
console.log(this.$store.getters.reservationsCurrentWeek);
switch (this.activeCalendar) {
case 'week':
// Commit with getter result
this.$store.commit('setShownReservations', this.$store.getters.reservationsCurrentWeek);
break;
The first print shows the expected result.
However when I print what value is actually passed inside the mutation as follows:
// TEST PRINT: return value of getter
console.log(this.getters.reservationsCurrentWeek);
// TEST PRINT: parameter passed to the mutation.
console.log(value);
state.shownReservations = value;
The parameter value is is not what the getter returned (but some old value). When I access the getter directly in my mutation it also gives a correct value. I don't get why I can't pass the result of the the getter as a parameter to a mutation commit and why it produces an unreliable value?
From where are you trying to access this.getters.reservationsCurrentWeek ? If you are accessing getters from mutation and getters and mutations placed in one file you can use
mutations: {
YOUR_MUTATION(state, {data, getters}) {
console.log(getters);
}
}
and then access getters. Or import getters from another file if it placed in separate file.
I want to perform an action after mutation has done it's job.
The mutation (SOME_MUTATION) is from same store and the action(test/testAction) is from a module
someAction(context, data) {
context.commit('SOME_MUTATION', data) // this should be committed first
context.dispatch('test/testAction') // this action should be dispatched after the above commit
}
Note: I don't know whether or not it is the default behaviour
you would change your someAction(context, data) to this
someAction({commit, dispatch}, data) {
call.action()
.then(=> {
commit('some_mutations',data)
dispatch('another_action')
}
}
using {commit, dispatch} will allow you use both inside action without having to say context. because actions only accept 2 parameters you pass both in as single object, so that you can still pass in the data parameter.
We created a simple MobX store to save an object. This MobX store also syncs with AsyncStorage every time fooObject is changed:
import { observable } from 'mobx';
class FooStore {
#observable fooObject = {};
setFooObject(newFooObject) {
this.fooObject = newFooObject;
this.syncWithAsyncStorage();
}
syncWithAsyncStorage() {
AsyncStorage.setItem('fooObject', JSON.stringify(this.fooObject));
}
This mobx store is then consumed by various screens in the app.
As you can see, we use RN AsyncStorage, which is kind of a LocalStorage but for native apps, to always store the current state of fooObject.
The question is: when the app is closed, we loose FooStore state, so we want to retrieve the last 'save' we did to AsyncStorage (which is persisted).
The problem is AsyncStorage is (as the name states) an ASYNC function, and we can't wait for a promise resolution in FooStore's constructor.
How one would accomplish this initial state initialization of this MobX store based on the resolved return value of AsyncStorage?
We thought about writing an initialization method (async method) in FooStore like this:
async initializeFromAsyncStorage() {
this.fooObject = await JSON.parse(await AsyncStorage.getItem('fooObject'));
}
And then call this method on app initialization in our root App.js.
But I'm not sure if this would present unintended side effects and if this is idiomatic mobx/react-native.
Yes, I would create an initialization method. If the instance is not dependent of the initial data, you could call it right after the instantiation:
const fooStore = new FooStore()
fooStore.initializeFromAsyncStorage()
export default fooStore
Thill will not guarantee that the initial data is loaded when the app is constructed, but since the data is an optional observable, your app will react to it if/when it’s available.
Remember that AsyncStorage.getItem will resolve null if it has not been set, so you might want to do:
async initializeFromAsyncStorage() {
const result = await AsyncStorage.getItem('fooObject')
if (result !== null) {
this.fooObject = JSON.parse(result)
}
}
I have a problem with iterating over mapGetters results.
My code looks like :
...mapGetters({
'shop' : 'getShops'
})
After that, when I iterate over the shops and change anything, it changes my parameters in state and it has impact on all of my app state. I need to change parameters on 'copy' of this getters but it also should be observable.
I've tried to assign mapGetters result to a computed variable but it also updated state
How can I achieve this?
here is a typical store getter:
const state = {
obj: {count:1}
}
const getters = {
obj: (state) => {
return state.obj
}
}
if you call a store by either mapGetters or directly through $store.state.obj, you are getting the object itself passed. The object javascritp provides is not immutable, and the nature of objects in js is such that if you make a change to the object when it's passed to a component, it will update in the store too.
There are two ways you can prevent objects not updating through the passed getter.
do not make changes to it EVER, just reference or copy values as needed when changes are needed.
return a copy of the object in the getter
I've personally only used approach 1, but if you need to return a copy for changing (2), you can use any one of these to create a copy
using ES6 spread operator (not a deep copy)
objCopy: (state) => {
return {...state.obj};
}
also a good option for ES6 (not a deep copy):
objCopy: (state) => {
return Object.assign({}, state.obj});
}
or the ES5 way, which creates a deep copy (so the children, grandchildren, etc don't succumb to same issue)
objCopy: (state) => {
return JSON.parse(JSON.stringify(state.obj));
}