Vuex: Why do we write mutations, actions and getters in uppercase? - vue.js

I'm wondering why do we write the function name of mutations, actions and getters in uppercase? Where does this convention come from?
export default {
SOME_MUTATION (state, payload) {
},
ANOTHER_MUTATION (state, payload) {
},
}

The accepted answer by Bert is a bit misleading. Constant variables are traditionally written in all caps, but the way it is used in the question does not make it a constant.
This allows the code to take advantage of tooling like linters
The official Vue.js documentation recommends using all caps, but as variables in an additional file. This makes it possible to require the available function names in other files and use auto-complete.
mutation-types.js:
export const SOME_MUTATION = 'SOME_MUTATION'
store.js:
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// we can use the ES2015 computed property name feature
// to use a constant as the function name
[SOME_MUTATION] (state) {
// mutate state
}
}
})
Please note the different writing-style here (computed property name with square brackets):
[SOME_MUTATION] (state) { }
If you just write the function names in all upper case (i.e. SOME_MUTATION(state) { }) the only benefit is a mere visual one (to separate Vuex functions from others), but in my opinion this does not make much sense. Stick to the one with computed property name ([SOME_MUTATION] (state)) to get all the benefits.

It is a long standing coding style to write constants in all uppercase.
From the Vuex documentation:
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
So, it's really just following a long standing tradition of naming constants in uppercase for the most part. It's not required.
Whether to use constants is largely a preference - it can be helpful in large projects with many developers, but it's totally optional if you don't like them

Related

mobx computed always update that computed doesn't work

import {makeAutoObservable} from "mobx";
class Test {
id = 0
constructor() {
makeAutoObservable(this)
}
get total() {
console.log('enss')
return 2;
}
}
const store = new Test();
export default store;
call:
import {isComputed, isComputedProp} from "mobx";
console.log(isComputedProp(Test, 'total'),Test.total,Test.total, Test.total, isComputedProp(Test, 'total'))
console output:
enss
enss
enss
true 2 2 2 true
the computed did not work and does not serve as a cache.
i using mobx 6.6 version in react 18.
Thank you very much for your answer!
This is explained in the docs here
It sometimes confuses people new to MobX, perhaps used to a library like Reselect, that if you create a computed property but don't use it anywhere in a reaction, it is not memoized and appears to be recomputed more often than necessary. For example, if we extended the above example with calling console.log(order.total) twice, after we called stop(), the value would be recomputed twice.
Basically it won't be cached if you use it outside of reactive context.
You can use computed({ keepAlive: true }) option to change this behaviour

Why Vue.js doesn't trigger reactivity when Component Props are changed in a helper function, outside of the Component scope(in a different file)?

I prefer to move functionality outside Components when the Component code becomes to crowded. So, this is a problem with which I have confronted several times when I have passed Component Props as arguments to functions that were outside of the Component scope, hosted in a different file as helper functions or as class methods. The mistake was fed also by the personal idea that I should use imutable objects everywere for better performance.
// the component file hosted in "MyComponent.vue"
<template>
<button #click="update">Please, update!</button>
</template>
<script>
import {
updatePrimitiveFn,
updateLiteralObjectFn,
updateArrayFn,
} from "../helpers.js";
export default {
name: "MyComponent",
data() {
return {
primitiveToBeUpdated: false,
arrayToBeUpdated: [],
literalObjectToBeUpdated: {},
};
},
methods: {
update() {
updatePrimitiveFn(this.primitiveToBeUpdated);
updatedLiteralObjectFn(this.literalObjectToBeUpdated);
updateArrayFn(this.arrayToBeUpdated);
console.log("primitiveToBeUpdated", this.primitiveToBeUpdated);
console.log("literalObjectToBeUpdated", this.literalObjectToBeUpdated);
console.log("arrayToBeUpdated", this.arrayToBeUpdated);
},
},
};
</script>
// the helpers functions hosted in "helpers.js"
export function updatePrimitiveFn(primitiveParameter) {
primitiveParameter = true; // it will not trigger reactivity, the argument it's a primitive and for primitives JavaScript copies the argument value to the formal parameter. For arguments that are objects, JavaScript copies the pointers of these objects to the formal parameter, keeping the link between component props and formal parameters. Pointers stores the memory address where some data lives.
}
export function updateLiteralObjectFn(literalObjectParameter) {
literalObjectParameter = { value: true }; // it will not trigger reactivity, it replaces the pointer of the received argument with a fresh new one.
}
export function updateArrayFn(arrayToBeUpdated) {
arrayToBeUpdated = [true]; // it will not trigger reactivity, it replaces the pointer of the received argument with a fresh new one.
}
The solution is to pass only objects, as arguments, to the external functions and mutate them as follows:
// the helpers functions hosted in "helpers.js"
export function updatePrimitiveFn(primitiveParameter) {
primitiveParameter = true; // it will not trigger reactivity, the argument it's a primitive and for primitives JavaScript copies the argument value to the formal parameter. For arguments that are objects, JavaScript copies the pointers of these objects to the formal parameter, keeping the link between component props and formal parameters. Pointers stores the memory address where some data lives.
}
export function updateLiteralObjectFn(literalObjectParameter) {
literalObjectParameter.value = true; // mutation keeps pointing same object in memory
}
export function updateArrayFn(arrayToBeUpdated) {
arrayToBeUpdated.push(true); mutation keeps pointing same object in memory
}
Check live demo: https://codesandbox.io/s/hardcore-volhard-mf6x0?file=/src/components/MyComponent.vue:0-179
For more information can be studied the subject "Pass by Value vs. Pass by Reference".

make a method globally available in Vue.js

In several Vue files this computed property checks if this.data is not an empty object:
computed: {
isLoaded() {
return !(this.data && Object.keys(this.data).length === 0 && this.data.constructor === Object); // checks if this.data is not empty
}
}
Then isLoaded is used to conditionally display content in the browser.
I'd like to refactor the code and create a global method somehow that can check if an object is empty so all the files that use this method can get it from a central spot.
Even after doing some reading on Vue mixins and plugins I'm not clear which one best fits this use case. Which one should be used for this? Or is there an altogether different approach that would be better to create a global method?
There are several ways to do it and it and approach depends on particular case. For your case I'd suggest you to create a separate folder for utils functions, where you could have smth like common.js. There you can just export your functions e.g.
export const emptyObj = (obj: any): any => Object.keys(obj).length === 0;
and import it in your component:
import { emptyObj } from "src/utils/common";
In this case it easier to organize your shared functions, easier to test them and have typescript support.

Vuex mutations and actions responsibilities

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.

vue2 observable mapGetters updated issue

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));
}