Vuejs computed and watch - vue.js

I was reading this Vue guide about computed and watcher properties.
I was wondering about this part: this.debouncedGetAnswer = _.debounce(this.getAnswer, 500) in the created property.
My question is
is this formula going to add this.debouncedGetAnswer in the data or methods option/property of the component? Because I can see that debouncedGetAnswer is accessed with this like you would have if it is inside the data or methods property of the component. Any this.propertyName would be added to the vue component?
Also in the watch question, how do I know that functions can have argument like that? I tried console log of newQuestion and oldQuestion and saw the old and new value of the input. I am a beginner and I've been seeing a lot of functions that have arguments that I do not know they come from, but it is like they are inherent or something.
Thank you!
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<!-- Since there is already a rich ecosystem of ajax libraries -->
<!-- and collections of general-purpose utility methods, Vue core -->
<!-- is able to remain small by not reinventing them. This also -->
<!-- gives you the freedom to use what you're familiar with. -->
<script src="https://cdn.jsdelivr.net/npm/axios#0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash#4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// whenever question changes, this function will run
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},
created: function () {
// _.debounce is a function provided by lodash to limit how
// often a particularly expensive operation can be run.
// In this case, we want to limit how often we access
// yesno.wtf/api, waiting until the user has completely
// finished typing before making the ajax request. To learn
// more about the _.debounce function (and its cousin
// _.throttle), visit: https://lodash.com/docs#debounce
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer: function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
}
}
})
</script>

debouncedGetAnswer won't be added to data or methods because you didn't add it there. It doesn't serve a good purpose to add it to methods because debouncing is generally should be applied to component instance, not all instances. It's possible to add it to data by adding it to data instead of created but this isn't needed because debouncedGetAnswer won't benefit from being reactive.
watch functions are callbacks. newQuestion and oldQuestion parameters are provided by Vue. This is specified in API documentation.

if you want to limit HTTP calls by debounce function, you can try to do these ways:
Use :value and #input to instead of v-model
<input :value="question" #input="debouncedGetAnswer($event)" />
implement debouncedGetAnswer on the methods of component
methods: {
debouncedGetAnswer: _.debounce(function (event) {
const inputValue = event.target.value;
this.getAnswer(inputValue);
}, 500),
getAnswer: function (newQuestionValue) {
// call API
}
full example
https://codesandbox.io/s/compassionate-germain-25kcd
<template>
<div id="watch-example">
<p>
Ask a yes/no question:
<input :value="question" #input="debouncedGetAnswer($event)" />
</p>
<p>{{ answer }}</p>
</div>
</template>
<script>
import _ from "lodash";
import axios from "axios";
export default {
name: "HelloWorld",
data: function () {
return {
question: "",
answer: "I cannot give you an answer until you ask a question!",
};
},
methods: {
debouncedGetAnswer: _.debounce(function (event) {
const inputValue = event.target.value;
this.getAnswer(inputValue);
}, 500),
getAnswer: function (newQuestionValue) {
if (newQuestionValue.indexOf("?") === -1) {
this.answer = "Questions usually contain a question mark. ;-)";
return;
}
this.answer = "Thinking...";
var vm = this;
axios
.get("https://yesno.wtf/api")
.then(function (response) {
vm.answer = _.capitalize(response.data.answer);
})
.catch(function (error) {
vm.answer = "Error! Could not reach the API. " + error;
});
},
},
};
</script>
<style scoped>
</style>

Related

Sendbird - Nuxt -Vuex - Do not mutate vuex store state outside mutation handlers [duplicate]

Why do I get this error:
Error [vuex] Do not mutate vuex store state outside mutation handlers.
What does it mean?
It happens when I try to type in the edit input file.
pages/todos/index.vue
<template>
<ul>
<li v-for="todo in todos">
<input type="checkbox" :checked="todo.done" v-on:change="toggle(todo)">
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button class="destroy" v-on:click="remove(todo)">delete</button>
<input class="edit" type="text" v-model="todo.text" v-todo-focus="todo == editedTodo" #blur="doneEdit(todo)" #keyup.enter="doneEdit(todo)" #keyup.esc="cancelEdit(todo)">
</li>
<li><input placeholder="What needs to be done?" autofocus v-model="todo" v-on:keyup.enter="add"></li>
</ul>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
data () {
return {
todo: '',
editedTodo: null
}
},
head () {
return {
title: this.$route.params.slug || 'all',
titleTemplate: 'Nuxt TodoMVC : %s todos'
}
},
fetch ({ store }) {
store.commit('todos/add', 'Hello World')
},
computed: {
todos () {
// console.log(this)
return this.$store.state.todos.list
}
},
methods: {
add (e) {
var value = this.todo && this.todo.trim()
if (value) {
this.$store.commit('todos/add', value)
this.todo = ''
}
},
toggle (todo) {
this.$store.commit('todos/toggle', todo)
},
remove (todo) {
this.$store.commit('todos/remove', todo)
},
doneEdit (todo) {
this.editedTodo = null
todo.text = todo.text.trim()
if (!todo.text) {
this.$store.commit('todos/remove', todo)
}
},
cancelEdit (todo) {
this.editedTodo = null
todo.text = this.beforeEditCache
},
},
directives: {
'todo-focus' (el, binding) {
if (binding.value) {
el.focus()
}
}
},
}
</script>
<style>
.done {
text-decoration: line-through;
}
</style>
stores/todos.js
export const state = () => ({
list: []
})
export const mutations = {
add (state, text) {
state.list.push({
text: text,
done: false
})
},
remove (state, todo) {
state.list.splice(state.list.indexOf(todo), 1)
},
toggle (state, todo) {
todo.done = !todo.done
}
}
Any ideas how I can fix this?
It could be a bit tricky to use v-model on a piece of state that belongs to Vuex.
and you have used v-model on todo.text here:
<input class="edit" type="text" v-model="todo.text" v-todo-focus="todo == editedTodo" #blur="doneEdit(todo)" #keyup.enter="doneEdit(todo)" #keyup.esc="cancelEdit(todo)">
use :value to read value and v-on:input or v-on:change to execute a method that perform the mutation inside an explicit Vuex mutation handler
This issue is handled here: https://vuex.vuejs.org/en/forms.html
Hello I have get the same problem and solve it with clone my object using one of the following:
{ ...obj} //spread syntax
Object.assign({}, obj)
JSON.parse(JSON.stringify(obj))
For your code I think you need to replace this part
computed: {
todos () {
// console.log(this)
return this.$store.state.todos.list
}
}
With this
computed: {
todos () {
// console.log(this)
return {...this.$store.state.todos.list}
}
}
I don't make sure if this is the best way but hope this helpful for other people that have the same issue.
This error may come from the fact you shallow cloned an object.
Meaning that you've tried to copy an object but an object is not a primitive type (like String or Number), hence it's passed by reference and not value.
Here you think that you cloned one object into the other, while you are still referencing the older one. Since you're mutating the older one, you got this nice warning.
Here is a GIF from Vue3's documentation (still relevant in our case).
On the left, it's showing an object (mug) being not properly cloned >> passed by reference.
On the right, it's properly cloned >> passed by value. Mutating this one does not mutate the original
The proper way to manage this error is to use lodash, this is how to load it efficiently in Nuxt:
Install lodash-es, eg: yarn add lodash-es, this is an optimized tree-shakable lodash ES module
you may also need to transpile it in your nuxt.config.js with the following
build: {
transpile: ['lodash-es'],
}
load it into your .vue components like this
<script>
import { cloneDeep } from 'lodash-es'
...
const properlyClonedObject = cloneDeep(myDeeplyNestedObject)
...
</script>
Few keys points:
lodash is recommended over JSON.parse(JSON.stringify(object)) because it does handle some edge-cases
we only load small functions from lodash and not the whole library thanks to this setup, so there is no penalty in terms of performance
lodash has a lot of well battle-tested useful functions, which is heavily lacking in JS (no core library)
UPDATE: structuredClone is also native and quite performant if you're looking for a solution for a deep copy, bypassing the need for Lodash at all.
There is no headache if you can use lodash
computed: {
...mapState({
todo: (state) => _.cloneDeep(state.todo)
})
}
Just in case someone's still being troubled by this,
I got my code working by making a duplicate/clone of the store state.
In your case, try something like this...
computed: {
todos () {
return [ ...this.$store.state.todos.list ]
}
}
It's basically a spread operator which results in making a clone of the todos.list array. With that, you're not directly changing the values of your state, just don't forget commit so your mutations will be saved in the store.
export default new Vuex.Store({
...
strict: true
})
try to comment "strict"
If you are using Vuex Modules, you might bump into this error if your module's data property is an object, instead of a function that returns an object, and you are sharing this Module between more than one Store.
So instead of:
// In stores/YourModule.js
export default {
state: { name: 'Foo' },
}
Change it to:
// In stores/YourModule.js
export default {
state: () => {
return { name: 'Foo' };
},
}
This is actually documented here:
Sometimes we may need to create multiple instances of a module, for
example:
Creating multiple stores that use the same module (e.g. To avoid
stateful singletons in the SSR (opens new window)when the
runInNewContext option is false or 'once'); Register the same module
multiple times in the same store. If we use a plain object to declare
the state of the module, then that state object will be shared by
reference and cause cross store/module state pollution when it's
mutated.
This is actually the exact same problem with data inside Vue
components. So the solution is also the same - use a function for
declaring module state (supported in 2.3.0+):
If your data is an array with objects inside. Below snippet is the solution
const toyData = await this.$store.dispatch(
`user/fetchCoinToys`,
payload
)
const msgList = toyData.msglist.map((data) => {
return { ...data }
})
I had to add mutation and call it instead of setting directly.
wrong:
someAction({state, rootState}) {
state.someValue = true;
}
right:
mutations: {
...
setSomeValue(state, val) {
state.someValue = val;
},
...
}
...
someAction({state, commit, rootState}) {
commit('setSomeValue', true);
}
It is not your case but if someone is using typescript and is having the same problem, adding this: any as the first param in your method or somewhere else should fix the problem

How to generate computed props on the fly while accessing the Vue instance?

I was wondering if there is a way of creating computed props programatically, while still accessing the instance to achieve dynamic values
Something like that (this being undefined below)
<script>
export default {
computed: {
...createDynamicPropsWithTheContext(this), // helper function that returns an object
}
}
</script>
On this question, there is a solution given by Linus: https://forum.vuejs.org/t/generating-computed-properties-on-the-fly/14833/4 looking like
computed: {
...mapPropsModels(['cool', 'but', 'static'])
}
This works fine but the main issue is that it's fully static. Is there a way to access the Vue instance to reach upon props for example?
More context
For testing purposes, my helper function is as simple as
export const createDynamicPropsWithTheContext = (listToConvert) => {
return listToConvert?.reduce((acc, curr) => {
acc[curr] = curr
return acc
}, {})
}
What I actually wish to pass down to this helper function (via this) are props that are matching a specific prefix aka starting with any of those is|can|has|show (I'm using a regex), that I do have access via this.$options.props in a classic parent/child state transfer.
The final idea of my question is mainly to avoid manually writing all the props manually like ...createDynamicPropsWithTheContext(['canSubmit', 'showModal', 'isClosed']) but have them populated programatically (this pattern will be required in a lot of components).
The props are passed like this
<my-component can-submit="false" show-modal="true" />
PS: it's can-submit and not :can-submit on purpose (while still being hacked into a falsy result right now!).
It's for the ease of use for the end user that will not need to remember to prefix with :, yeah I know...a lot of difficulty just for a semi-colon that could follow Vue's conventions.
You could use the setup() hook, which receives props as its first argument. Pass the props argument to createDynamicPropsWithTheContext, and spread the result in setup()'s return (like you had done previously in the computed option):
import { createDynamicPropsWithTheContext } from './props-utils'
export default {
⋮
setup(props) {
return {
...createDynamicPropsWithTheContext(props),
}
}
}
demo
If the whole thing is for avoiding using a :, then you might want to consider using a simple object (or array of objects) as data source. You could just iterate over a list and bind the data to the components generated. In this scenario the only : used are in the objects
const comps = [{
"can-submit": false,
"show-modal": true,
"something-else": false,
},
{
"can-submit": true,
"show-modal": true,
"something-else": false,
},
{
"can-submit": false,
"show-modal": true,
"something-else": true,
},
]
const CustomComponent = {
setup(props, { attrs }) {
return {
attrs
}
},
template: `
<div
v-bind="attrs"
>{{ attrs }}</div>
`
}
const vm = Vue.createApp({
setup() {
return {
comps
}
},
template: `
<custom-component
v-for="(item, i) in comps"
v-bind="item"
></custom-component>
`
})
vm.component('CustomComponent', CustomComponent)
vm.mount('#app')
<script src="https://unpkg.com/vue#3"></script>
<div id="app">{{ message }}</div>
Thanks to Vue's Discord Cathrine and skirtle folks, I achieved to get it working!
Here is the thread and here is the SFC example that helped me, especially this code
created () {
const magicIsShown = computed(() => this.isShown === true || this.isShown === 'true')
Object.defineProperty(this, 'magicIsShown', {
get () {
return magicIsShown.value
}
})
}
Using Object.defineProperty(this... is helping keeping the whole state reactive and the computed(() => can reference some other prop (which I am looking at in my case).
Using a JS object could be doable but I have to have it done from the template (it's a lower barrier to entry).
Still, here is the solution I came up with as a global mixin imported in every component.
// helper functions
const proceedIfStringlean = (propName) => /^(is|can|has|show)+.*/.test(propName)
const stringleanCase = (string) => 'stringlean' + string[0].toUpperCase() + string.slice(1)
const computeStringlean = (value) => {
if (typeof value == 'string') {
return value == 'true'
}
return value
}
// the actual mixin
const generateStringleans = {
created() {
for (const [key, _value] of Object.entries(this.$props)) {
if (proceedIfStringlean(key)) {
const stringleanComputed = computed(() => this[key])
Object.defineProperty(this, stringleanCase(key), {
get() {
return computeStringlean(stringleanComputed.value)
},
// do not write any `set()` here because this is just an overlay
})
}
}
},
}
This will scan every .vue component, get the passed props and if those are prefixed with either is|can|has|show, will create a duplicated counter-part with a prefix of stringlean + pass the initial prop into a method (computeStringlean in my case).
Works great, there is no devtools support as expected since we're wiring it directly in vanilla JS.

Vuex registerModule method registers an empty module

I have two modules. One load statically, the other dynamically.
StaticLoadingStore.js:
export default {
namespaced: false,
state() {
return {
propertySL: 'Some value from a statically loaded module',
}
},
getters: {
getPropertySL(state) {
return state.propertySL
},
},
}
DynamicLoadingStore.js
export default {
namespaced: true,
state() {
return {
propertyDL: 'Some value from a dynamically loaded module',
}
},
getters: {
getPropertyDL(state) {
return state.propertyDL
},
},
}
Dynamically loaded module shows that it is empty. Why?
HelloWorld.vue:
<template>
<div>
<h1>SL</h1>
<h5>propertySL:</h5>
<p>{{ propertySL }}</p>
<h5>stateSL:</h5>
<code>{{stateSL}} </code>
<h1>DL</h1>
<h5>propertyDL:</h5>
<p>{{ propertyDL===undefined?'undefined':propertyDL }}</p>
<!-- return undefined -->
<h5>stateDL:</h5>
<code>{{stateDL}} </code>
<!-- return {} -->
</div>
</template>
<script>
import SLModule from '../StaticLoadingStore'
const DLModule = () => import('../DynamicLoadingStore.js');
export default {
data: () => ({
stateSL: '',
stateDL: '',
}),
computed: {
propertySL() {
return this.$store.getters['getPropertySL']
},
propertyDL() {
return this.$store.getters['dlModule/getPropertyDL']
},
},
created() {
this.$store.registerModule('slModule', SLModule);
this.stateSL = JSON.stringify(this.$store.state['slModule'], null, 2);
this.$store.registerModule('dlModule', DLModule());
this.stateDL = JSON.stringify(this.$store.state['dlModule'], null, 2);
}
}
</script>
My knowledge in vue and js is very limited, and I ask the question through Google translator, so I apologize in advance for incompetence.
Without waiting for an answer, he began to experiment.
That's how it worked.
DynamicLoadingStore.js
...
async created() {
const moduleLoader = await DLModule();
this.$store.registerModule('dlModule', moduleLoader.default);
...
But why this is not as recommended in the examples is not clear.
New problem. Reactivity does not work. alert(this.$store.getters['dlModule/getPropertyDL'])
gives expected data.
But the propertySL in template is empty. Tell me what's wrong, please.
But why this is not as recommended in the examples is not clear.
If you talking about this official guide Dynamic Module Registration. I think the author doesn't want to specify how to get the module since there are a lot of ways to do.
In your example I think both modules should call dynamic module, static module is the module that declared at store creation.
But you import it with different methods which are static import and dynamic import. You can read more about import from MDN.
To use dynamic import, there is no need to wrap import statement with function:
...
await import('../DynamicLoadingStore.js')
...
...
// This will useful when you use dynamic component
() => import('../DynamicLoadingStore.js')
...
New problem. Reactivity does not work.
alert(this.$store.getters['dlModule/getPropertyDL']) gives expected
data.
But the propertySL in template is empty. Tell me what's wrong, please.
If you register slModule before dlModule, the propertySL should still work fine but not propertyDL.
The reason is this is the how computed property works, since you are using async created instead of created, the computed property doesn't wait until async created finished. So when Vue try to compute the dependency of the property it cannot do correctly because your getters will return undefined.
You can solve this problem by use another data to trigger computed property to recompute like this:
this.dlModuleReady && this.$store.getters["dlModule/getPropertyDL"];
See example.

How to populate form from store state data returned from vuex with getters?

I have tried to use the same form for creating and for editing purpose. i have passed the id with props from one component to other and it works fine. using that id i have tried to fetch the data from vuex store state with the help of the getters. the getters returned the data correctly. But i can't populate to the form. i have tried the create, computed and mount properties but i does not work.
i have tried to populate the form using mount, create and computed.
i expect the output the form to be populate when i click the edit button but nothing to see in the form.
Template code
<template>
<b-form-group
label-cols="12"
label="Minimum Salary: "
>
<b-form-input
id="input-1"
v-model="record.min_salary"
type="number"
></b-form-input>
</b-form-group>
<b-form-group
label-cols="12"
label-cols-lg="3"
label-for="input-2"
label="Maximum Salary: "
label-align-sm="right"
label-align="left"
>
<b-form-input
id="input-2"
v-model="record.max_salary"
type="number"
></b-form-input>
</b-form-group>
<b-form-group
label-cols="12"
label-cols-lg="3"
label-for="input-2"
label="Tax Rate: "
label-align-sm="right"
label-align="left"
>
<b-form-input
id="input-2"
v-model="record.tax_rate"
type="number"
></b-form-input>
</b-form-group>
<b-form-group
label-cols="12"
label-cols-lg="3"
label-for="input-2"
label="Deductible: "
label-align-sm="right"
label-align="left"
>
<b-form-input
id="input-2"
v-model="record.deductible"
type="number"
></b-form-input>
</b-form-group>
</b-form>
</template>
Vue Script Code
<script>
import { mapGetters, mapActions } from "vuex";
export default {
props: ["id"],
data: () => ({
update: false,
record: {
min_salary: "",
max_salary: "",
tax_rate: "",
deductible: ""
}
}),
created() {
if (this.id != null) {
this.update = true;
this.fetchIncomeTaxRate(this.id);
this.createoredit = "EDIT";
}
},
mounted() {
if (this.id != null) {
this.record.min_salary = this.getIncomeTaxRate.min_salary;
this.record.max_salary = this.getIncomeTaxRate.max_salary;
this.record.tax_rate = this.getIncomeTaxRate.tax_rate;
this.record.deductible = this.getIncomeTaxRate.deductible;
}
},
methods: { ...mapActions(["fetchIncomeTaxRate"])},
computed: {...mapGetters(["getIncomeTaxRate"])}
};
</script>
Store Code
import axios from "axios";
import commonAPI from "../commonAPI";
const state = {
incomeTaxRate: []
};
const mutations = {
setIncomeTaxRate: (state, incomeTaxRate) =>
(state.incomeTaxRate = incomeTaxRate)
};
const getters = {
getIncomeTaxRate: state => {
return state.incomeTaxRate;
}
};
const actions = {
async fetchIncomeTaxRate({ commit }, id) {
const response = await axios.get(
commonAPI.PAYROLL_BASE_URL + `/income-tax-rates/${id}`
);
commit("setIncomeTaxRate", response.data);
}
};
export default {
state,
mutations,
getters,
actions
};
It's a little difficult to give a concrete answer without seeing the store code but you're going to have to wait until the store has finished fetching the data before trying to read it into your form.
Probably the easiest way is to use the Promise returned by fetchIncomeTaxRate. All actions return a Promise when called, even if you don't return one yourself. Make sure you're returning a suitable Promise, i.e. one that doesn't resolve until the data is loaded. If you're loading the data using a library such as axios that supports Promises then this should be very easy, you just need to return the Promise it returns to you.
Once you've returned a suitable Promise you can do something like this:
this.fetchIncomeTaxRate(this.id).then(() => {
this.record.min_salary = this.getIncomeTaxRate.min_salary;
// ... others here
});
You may want to put in extra code to prevent the form being seen/edited until the relevant data is populated.
An alternative to using Promises would be to use watch to wait for getIncomeTaxRate to change. However it would be difficult to ensure that you only react to exactly the right change.
Update:
Now that you've posted your store code I've tried running your code myself (as close as I could get) and record was updated fine if I used then in the way I outlined above. Most likely this suggests that there's another problem that isn't in the code you've posted. For example, it might be that the data returned from the server isn't in exactly the format you're expecting.
To diagnose further I suggest putting in some console logging. I'd put some in at the top of the then for starters:
this.fetchIncomeTaxRate(this.id).then(() => {
console.log('in then', this.getIncomeTaxRate, this.getIncomeTaxRate.min_salary);
this.record.min_salary = this.getIncomeTaxRate.min_salary;
// ... others here
});
I'd also put some in fetchIncomeTaxRate too:
async fetchIncomeTaxRate({ commit }, id) {
console.log('called fetchIncomeTaxRate');
const response = await axios.get(
commonAPI.PAYROLL_BASE_URL + `/income-tax-rates/${id}`
);
commit("setIncomeTaxRate", response.data);
console.log('committed', response.data);
}
Not only should the logged values be correct but the order of the log messages is important too. The data should be committed before the then is called.

Vuex - Do not mutate vuex store state outside mutation handlers

Why do I get this error:
Error [vuex] Do not mutate vuex store state outside mutation handlers.
What does it mean?
It happens when I try to type in the edit input file.
pages/todos/index.vue
<template>
<ul>
<li v-for="todo in todos">
<input type="checkbox" :checked="todo.done" v-on:change="toggle(todo)">
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button class="destroy" v-on:click="remove(todo)">delete</button>
<input class="edit" type="text" v-model="todo.text" v-todo-focus="todo == editedTodo" #blur="doneEdit(todo)" #keyup.enter="doneEdit(todo)" #keyup.esc="cancelEdit(todo)">
</li>
<li><input placeholder="What needs to be done?" autofocus v-model="todo" v-on:keyup.enter="add"></li>
</ul>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
data () {
return {
todo: '',
editedTodo: null
}
},
head () {
return {
title: this.$route.params.slug || 'all',
titleTemplate: 'Nuxt TodoMVC : %s todos'
}
},
fetch ({ store }) {
store.commit('todos/add', 'Hello World')
},
computed: {
todos () {
// console.log(this)
return this.$store.state.todos.list
}
},
methods: {
add (e) {
var value = this.todo && this.todo.trim()
if (value) {
this.$store.commit('todos/add', value)
this.todo = ''
}
},
toggle (todo) {
this.$store.commit('todos/toggle', todo)
},
remove (todo) {
this.$store.commit('todos/remove', todo)
},
doneEdit (todo) {
this.editedTodo = null
todo.text = todo.text.trim()
if (!todo.text) {
this.$store.commit('todos/remove', todo)
}
},
cancelEdit (todo) {
this.editedTodo = null
todo.text = this.beforeEditCache
},
},
directives: {
'todo-focus' (el, binding) {
if (binding.value) {
el.focus()
}
}
},
}
</script>
<style>
.done {
text-decoration: line-through;
}
</style>
stores/todos.js
export const state = () => ({
list: []
})
export const mutations = {
add (state, text) {
state.list.push({
text: text,
done: false
})
},
remove (state, todo) {
state.list.splice(state.list.indexOf(todo), 1)
},
toggle (state, todo) {
todo.done = !todo.done
}
}
Any ideas how I can fix this?
It could be a bit tricky to use v-model on a piece of state that belongs to Vuex.
and you have used v-model on todo.text here:
<input class="edit" type="text" v-model="todo.text" v-todo-focus="todo == editedTodo" #blur="doneEdit(todo)" #keyup.enter="doneEdit(todo)" #keyup.esc="cancelEdit(todo)">
use :value to read value and v-on:input or v-on:change to execute a method that perform the mutation inside an explicit Vuex mutation handler
This issue is handled here: https://vuex.vuejs.org/en/forms.html
Hello I have get the same problem and solve it with clone my object using one of the following:
{ ...obj} //spread syntax
Object.assign({}, obj)
JSON.parse(JSON.stringify(obj))
For your code I think you need to replace this part
computed: {
todos () {
// console.log(this)
return this.$store.state.todos.list
}
}
With this
computed: {
todos () {
// console.log(this)
return {...this.$store.state.todos.list}
}
}
I don't make sure if this is the best way but hope this helpful for other people that have the same issue.
This error may come from the fact you shallow cloned an object.
Meaning that you've tried to copy an object but an object is not a primitive type (like String or Number), hence it's passed by reference and not value.
Here you think that you cloned one object into the other, while you are still referencing the older one. Since you're mutating the older one, you got this nice warning.
Here is a GIF from Vue3's documentation (still relevant in our case).
On the left, it's showing an object (mug) being not properly cloned >> passed by reference.
On the right, it's properly cloned >> passed by value. Mutating this one does not mutate the original
The proper way to manage this error is to use lodash, this is how to load it efficiently in Nuxt:
Install lodash-es, eg: yarn add lodash-es, this is an optimized tree-shakable lodash ES module
you may also need to transpile it in your nuxt.config.js with the following
build: {
transpile: ['lodash-es'],
}
load it into your .vue components like this
<script>
import { cloneDeep } from 'lodash-es'
...
const properlyClonedObject = cloneDeep(myDeeplyNestedObject)
...
</script>
Few keys points:
lodash is recommended over JSON.parse(JSON.stringify(object)) because it does handle some edge-cases
we only load small functions from lodash and not the whole library thanks to this setup, so there is no penalty in terms of performance
lodash has a lot of well battle-tested useful functions, which is heavily lacking in JS (no core library)
UPDATE: structuredClone is also native and quite performant if you're looking for a solution for a deep copy, bypassing the need for Lodash at all.
There is no headache if you can use lodash
computed: {
...mapState({
todo: (state) => _.cloneDeep(state.todo)
})
}
Just in case someone's still being troubled by this,
I got my code working by making a duplicate/clone of the store state.
In your case, try something like this...
computed: {
todos () {
return [ ...this.$store.state.todos.list ]
}
}
It's basically a spread operator which results in making a clone of the todos.list array. With that, you're not directly changing the values of your state, just don't forget commit so your mutations will be saved in the store.
export default new Vuex.Store({
...
strict: true
})
try to comment "strict"
If you are using Vuex Modules, you might bump into this error if your module's data property is an object, instead of a function that returns an object, and you are sharing this Module between more than one Store.
So instead of:
// In stores/YourModule.js
export default {
state: { name: 'Foo' },
}
Change it to:
// In stores/YourModule.js
export default {
state: () => {
return { name: 'Foo' };
},
}
This is actually documented here:
Sometimes we may need to create multiple instances of a module, for
example:
Creating multiple stores that use the same module (e.g. To avoid
stateful singletons in the SSR (opens new window)when the
runInNewContext option is false or 'once'); Register the same module
multiple times in the same store. If we use a plain object to declare
the state of the module, then that state object will be shared by
reference and cause cross store/module state pollution when it's
mutated.
This is actually the exact same problem with data inside Vue
components. So the solution is also the same - use a function for
declaring module state (supported in 2.3.0+):
If your data is an array with objects inside. Below snippet is the solution
const toyData = await this.$store.dispatch(
`user/fetchCoinToys`,
payload
)
const msgList = toyData.msglist.map((data) => {
return { ...data }
})
I had to add mutation and call it instead of setting directly.
wrong:
someAction({state, rootState}) {
state.someValue = true;
}
right:
mutations: {
...
setSomeValue(state, val) {
state.someValue = val;
},
...
}
...
someAction({state, commit, rootState}) {
commit('setSomeValue', true);
}
It is not your case but if someone is using typescript and is having the same problem, adding this: any as the first param in your method or somewhere else should fix the problem