i'm trying to have a computed property in vuejs associated to a es6 class.
My Vue instance looks like this:
...
props: ['customClass'],
computed: {
localClass: {
get() {
return this.customClass
},
set (value) {
console.log("changed")
}
}
}
...
My class looks like this
class CustomClass {
constructor () {
this.selected = false
}
}
If i try to do something like that:
this.localClass.selected = true
but the setter is never called, like the reactivity has been lost and i don't understand why.
I also try:
Vue.set(this.localClass, 'selected', true)
I pass customClass as a prop, but even creating a new instance directly in the component it doesn't change the result.
In vuejs docs i don't recall a section talking about reactivity problem in es6 class, so i was wondering if someone know why and how to make my class reactive.
Thanks in advance
The setter of a computed property, say myComputedProperty, is triggered when you assign to that property (e.g. this.myComputedProperty = {something: 'else'}.
What you probably are looking for is a watcher, more specifically, a watcher with deep: true, such as:
watch: {
localClass: {
deep: true,
handler() {
out.innerHTML += "watched!";
}
}
},
Demo below.
class CustomClass {
constructor() {
this.selected = false
}
}
Vue.component('custom', {
template: '#custom',
props: ['customClass'],
computed: {
localClass: {
get() {
return this.customClass
},
set(value) {
out.innerHTML += "changed!\n";
}
}
},
watch: {
localClass: {
deep: true,
handler() {
out.innerHTML += "watched!\n";
}
}
},
methods: {
assignToSelected() {
this.localClass.selected = true
},
assignToLocalClass() {
this.localClass = {
selected: true
}
}
}
});
new Vue({
el: '#app',
data: {
test: new CustomClass()
},
})
#out { background: black; color: gray; }
span { font-size: x-small; font-family: verdana }
<script src="https://unpkg.com/vue"></script>
<template id="custom">
<div>
{{ localClass }}
<br>
<button #click="assignToSelected">assignToSelected</button>
<span>Note: will trigger "watched!" just once, because, since the value is hardcoded in the method (see code) subsequent clicks won't modify the value.</span>
<br><br>
<button #click="assignToLocalClass">assignToLocalClass</button>
<span>Note: assignToLocalClass() will trigger the computed setter, but wont trigger the watcher because the computed setter currently sets nothing, so nothing changed for the watcher to trigger.</span>
</div>
</template>
<div id="app">
<custom :custom-class="test"></custom>
</div>
<pre id="out"></pre>
Related
According to the documentation I should be able to use computed properties as v-model in Vue as long as I define get/set methods, but in my case it doesn't work:
export default{
template: `
<form class="add-upload" #submit.prevent="return false">
<label><input type="checkbox" v-model="options.test" /> test </label>
</form>
`,
computed: {
options: {
get(){
console.log('get');
return {test: false};
},
set(value){
console.log('set');
},
},
}
}
Apparently set is not called when I check/uncheck the input.
But get is called when the component is displayed...
Edit: After reading in the comments that you rely on the localstorage, I can only suggest you to take the Vuex approach and use a persistence library to handle the localstorage. (https://www.npmjs.com/package/vuex-persist)
This way, your localstorage will always be linked to your app and you don't have to mess with getItem/setItem everytime.
Looking at your approach, I assume you have your reasons to use a computed property over a data property.
The problem happens because your computed property returns an object defined nowhere but in the get handler.
Whatever you try, you won't be able to manipulate that object in the set handler.
The get and set must be linked to a common reference. A data property, as many suggested, or a source of truth in your app (a Vuex instance is a very good example).
this way, your v-model will work flawlessly with the set handler of your computed property.
Here's a working fiddle demonstrating the explanation:
With Vuex
const store = new Vuex.Store({
state: {
// your options object is predefined in the store so Vue knows about its structure already
options: {
isChecked: false
}
},
mutations: {
// the mutation handler assigning the new value
setIsCheck(state, payload) {
state.options.isChecked = payload;
}
}
});
new Vue({
store: store,
el: "#app",
computed: {
options: {
get() {
// Here we return the options object as depicted in your snippet
return this.$store.state.options;
},
set(checked) {
// Here we use the checked property returned by the input and we commit a Vuex mutation which will mutate the state
this.$store.commit("setIsCheck", checked);
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
<div id="app">
<h2>isChecked: {{ options.isChecked }}</h2>
<input type="checkbox" v-model="options.isChecked" />
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex#2.0.0"></script>
With a data property
new Vue({
el: "#app",
data: {
options: {
isChecked: false
}
},
computed: {
computedOptions: {
get() {
return this.options;
},
set(checked) {
this.options.isChecked = checked;
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
<div id="app">
<h2>isChecked: {{ computedOptions.isChecked }}</h2>
<input type="checkbox" v-model="computedOptions.isChecked" />
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
Your approach is a bit special IMHO but, again, you must have your reasons to do so.
The very simple explanation here in code. computed properties are dependent on other data/reactive variables. If only when the reactive properties changed their values and if same property used to compute some other computed properties then the computed property would become reactive.
this way we must set values and get in setter and getter methods.
new Vue({
el: '#app',
data: {
message: 'Use computed property on input',
foo:0,
isChecked:true
},
computed:{
bar:{
get: function(){
return this.foo;
},
set: function(val){
this.foo = val;
}
},
check:{
get: function(){
return this.isChecked;
},
set: function(val){
this.isChecked = val;
}
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ message }} Text</p>
<input type="text" v-model="bar" />
{{bar}}
<br/>
<p>{{ message }} Checkbox</p>
<input type="checkbox" v-model="check" />
{{check}}
</div>
Instead of a computed getter/setter, use a local data prop, initialized to the target localStorage item; and a deep watcher (which detects changes on any subproperty) that sets localStorage upon change. This allows you to still use v-model with the local data prop, while observing changes to the object's subproperties.
Steps:
Declare a local data prop (named options) that is initialized to the current value of localStorage:
export default {
data() {
return {
options: {}
}
},
mounted() {
const myData = localStorage.getItem('my-data')
this.options = myData ? JSON.parse(myData) : {}
},
}
Declare a watch on the data prop (options), setting deep=true and handler to a function that sets localStorage with the new value:
export default {
watch: {
options: {
deep: true,
handler(options) {
localStorage.setItem('my-data', JSON.stringify(options))
}
}
},
}
demo
It seems the problem is both in the presence of options and the return value of the getter.
You could try this:
let options;
try {
options = JSON.parse(localStorage.getItem("options"));
}
catch(e) {
// default values
options = { test: true };
}
function saveOptions(updates) {
localStorage.setItem("options", JSON.stringify({ ...options, ...updates }));
}
export default{
template: `
<form class="add-upload" #submit.prevent="return false">
<label><input type="checkbox" v-model="test" /> test </label>
</form>`,
computed: {
test: {
get() {
console.log('get');
return options.test;
},
set(value) {
console.log('set', value);
saveOptions({ test: value });
},
},
}
}
Hope this helps.
I'm not familiar if there's a computed set method that could work here, but there's a few other approaches to solving the problem.
If you want a singular getter for mutating the data, you can use an event based method for setting the data. This method is my favorite:
export default {
template: `
<form class="add-upload" #submit.prevent="">
<label for="test"> test </label>
{{options.test}}
<input id="test" type="checkbox" v-model="options.test" #input="setOptions({test: !options.test})"/>
</form>
`,
data() {
return {
optionsData: {
test: false
}
}
},
computed: {
options: {
get() {
return this.optionsData;
},
},
},
methods: {
setOptions(options) {
this.$set(this, "optionsData", { ...this.optionsData, ...options })
}
}
}
If you're not really doing anything in the get/set you can just use the data option
export default {
template: `
<form class="add-upload" #submit.prevent="">
<label for="test"> test </label>
{{options.test}}
<input id="test" type="checkbox" v-model="options.test" />
</form>
`,
data() {
return {
options: {
test: false
}
}
}
}
Then there's also the option of get/set for every property
export default {
template: `
<form class="add-upload" #submit.prevent="">
<label for="test"> test </label>
{{test}}
<input id="test" type="checkbox" v-model="test" />
</form>
`,
data() {
return {
optionsData: {
test: false
}
}
},
computed: {
test: {
get() {
return this.optionsData.test;
},
set(value) {
this.optionsData.test = value
}
},
},
}
The return value of Vue computed properties are not automatically made reactive. Because you are returning a plain object, and because you're assigning to a property within the computed property, the setter will not trigger.
You have two problems you need to solve, one problem's solution is to store a reactive version of your computed property value (see Vue.observable()). The next problem is a bit more nuanced, I'd need to know why you want to hook in to the setter. My best guess without more information would be that you're actually looking to perform side-effects. In that case, you should watch the value for changes (see vm.$watch()).
Here's how I'd write that component based on the assumptions above.
export default {
template: `
<form class="add-upload" #submit.prevent="return false">
<label><input type="checkbox" v-model="options.test" /> test </label>
</form>
`,
computed: {
options(vm) {
return (
vm._internalOptions ||
(vm._internalOptions = Vue.observable({ test: false }))
)
},
},
watch: {
"options.test"(value, previousValue) {
console.log("set")
},
},
}
If you need to trigger side-effects based on anything changing on options, You can deeply watch it. The biggest caveat though is that the object must be reactive (solved by Vue.observable() or defining it in the data option).
export default {
watch: {
options: {
handler(value, previousValue) {
console.log("set")
},
deep: true,
},
},
}
According to the documentation I should be able to use computed properties as v-model in Vue as long as I define get/set methods, but in my case it doesn't work:
export default{
template: `
<form class="add-upload" #submit.prevent="return false">
<label><input type="checkbox" v-model="options.test" /> test </label>
</form>
`,
computed: {
options: {
get(){
console.log('get');
return {test: false};
},
set(value){
console.log('set');
},
},
}
}
Apparently set is not called when I check/uncheck the input.
But get is called when the component is displayed...
Edit: After reading in the comments that you rely on the localstorage, I can only suggest you to take the Vuex approach and use a persistence library to handle the localstorage. (https://www.npmjs.com/package/vuex-persist)
This way, your localstorage will always be linked to your app and you don't have to mess with getItem/setItem everytime.
Looking at your approach, I assume you have your reasons to use a computed property over a data property.
The problem happens because your computed property returns an object defined nowhere but in the get handler.
Whatever you try, you won't be able to manipulate that object in the set handler.
The get and set must be linked to a common reference. A data property, as many suggested, or a source of truth in your app (a Vuex instance is a very good example).
this way, your v-model will work flawlessly with the set handler of your computed property.
Here's a working fiddle demonstrating the explanation:
With Vuex
const store = new Vuex.Store({
state: {
// your options object is predefined in the store so Vue knows about its structure already
options: {
isChecked: false
}
},
mutations: {
// the mutation handler assigning the new value
setIsCheck(state, payload) {
state.options.isChecked = payload;
}
}
});
new Vue({
store: store,
el: "#app",
computed: {
options: {
get() {
// Here we return the options object as depicted in your snippet
return this.$store.state.options;
},
set(checked) {
// Here we use the checked property returned by the input and we commit a Vuex mutation which will mutate the state
this.$store.commit("setIsCheck", checked);
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
<div id="app">
<h2>isChecked: {{ options.isChecked }}</h2>
<input type="checkbox" v-model="options.isChecked" />
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex#2.0.0"></script>
With a data property
new Vue({
el: "#app",
data: {
options: {
isChecked: false
}
},
computed: {
computedOptions: {
get() {
return this.options;
},
set(checked) {
this.options.isChecked = checked;
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
<div id="app">
<h2>isChecked: {{ computedOptions.isChecked }}</h2>
<input type="checkbox" v-model="computedOptions.isChecked" />
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
Your approach is a bit special IMHO but, again, you must have your reasons to do so.
The very simple explanation here in code. computed properties are dependent on other data/reactive variables. If only when the reactive properties changed their values and if same property used to compute some other computed properties then the computed property would become reactive.
this way we must set values and get in setter and getter methods.
new Vue({
el: '#app',
data: {
message: 'Use computed property on input',
foo:0,
isChecked:true
},
computed:{
bar:{
get: function(){
return this.foo;
},
set: function(val){
this.foo = val;
}
},
check:{
get: function(){
return this.isChecked;
},
set: function(val){
this.isChecked = val;
}
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ message }} Text</p>
<input type="text" v-model="bar" />
{{bar}}
<br/>
<p>{{ message }} Checkbox</p>
<input type="checkbox" v-model="check" />
{{check}}
</div>
Instead of a computed getter/setter, use a local data prop, initialized to the target localStorage item; and a deep watcher (which detects changes on any subproperty) that sets localStorage upon change. This allows you to still use v-model with the local data prop, while observing changes to the object's subproperties.
Steps:
Declare a local data prop (named options) that is initialized to the current value of localStorage:
export default {
data() {
return {
options: {}
}
},
mounted() {
const myData = localStorage.getItem('my-data')
this.options = myData ? JSON.parse(myData) : {}
},
}
Declare a watch on the data prop (options), setting deep=true and handler to a function that sets localStorage with the new value:
export default {
watch: {
options: {
deep: true,
handler(options) {
localStorage.setItem('my-data', JSON.stringify(options))
}
}
},
}
demo
It seems the problem is both in the presence of options and the return value of the getter.
You could try this:
let options;
try {
options = JSON.parse(localStorage.getItem("options"));
}
catch(e) {
// default values
options = { test: true };
}
function saveOptions(updates) {
localStorage.setItem("options", JSON.stringify({ ...options, ...updates }));
}
export default{
template: `
<form class="add-upload" #submit.prevent="return false">
<label><input type="checkbox" v-model="test" /> test </label>
</form>`,
computed: {
test: {
get() {
console.log('get');
return options.test;
},
set(value) {
console.log('set', value);
saveOptions({ test: value });
},
},
}
}
Hope this helps.
I'm not familiar if there's a computed set method that could work here, but there's a few other approaches to solving the problem.
If you want a singular getter for mutating the data, you can use an event based method for setting the data. This method is my favorite:
export default {
template: `
<form class="add-upload" #submit.prevent="">
<label for="test"> test </label>
{{options.test}}
<input id="test" type="checkbox" v-model="options.test" #input="setOptions({test: !options.test})"/>
</form>
`,
data() {
return {
optionsData: {
test: false
}
}
},
computed: {
options: {
get() {
return this.optionsData;
},
},
},
methods: {
setOptions(options) {
this.$set(this, "optionsData", { ...this.optionsData, ...options })
}
}
}
If you're not really doing anything in the get/set you can just use the data option
export default {
template: `
<form class="add-upload" #submit.prevent="">
<label for="test"> test </label>
{{options.test}}
<input id="test" type="checkbox" v-model="options.test" />
</form>
`,
data() {
return {
options: {
test: false
}
}
}
}
Then there's also the option of get/set for every property
export default {
template: `
<form class="add-upload" #submit.prevent="">
<label for="test"> test </label>
{{test}}
<input id="test" type="checkbox" v-model="test" />
</form>
`,
data() {
return {
optionsData: {
test: false
}
}
},
computed: {
test: {
get() {
return this.optionsData.test;
},
set(value) {
this.optionsData.test = value
}
},
},
}
The return value of Vue computed properties are not automatically made reactive. Because you are returning a plain object, and because you're assigning to a property within the computed property, the setter will not trigger.
You have two problems you need to solve, one problem's solution is to store a reactive version of your computed property value (see Vue.observable()). The next problem is a bit more nuanced, I'd need to know why you want to hook in to the setter. My best guess without more information would be that you're actually looking to perform side-effects. In that case, you should watch the value for changes (see vm.$watch()).
Here's how I'd write that component based on the assumptions above.
export default {
template: `
<form class="add-upload" #submit.prevent="return false">
<label><input type="checkbox" v-model="options.test" /> test </label>
</form>
`,
computed: {
options(vm) {
return (
vm._internalOptions ||
(vm._internalOptions = Vue.observable({ test: false }))
)
},
},
watch: {
"options.test"(value, previousValue) {
console.log("set")
},
},
}
If you need to trigger side-effects based on anything changing on options, You can deeply watch it. The biggest caveat though is that the object must be reactive (solved by Vue.observable() or defining it in the data option).
export default {
watch: {
options: {
handler(value, previousValue) {
console.log("set")
},
deep: true,
},
},
}
I just started using Vue and I have a very simple issue that I just can't get to work! I'm trying to create a mounted event that runs a method with a specific parameter inside it to alter the "show" value of an element. here is the code:
export default {
data(){
return {
one: false,
}
},
methods: {
show: function(el) {
this.el = true;
}
},
mounted(){
this.show(this.one)
}
}
I want "el" to be just a generic placeholder for whatever "data" name is passed into the method. in the future I may not only have "one" but also "two", "three" and "four". I want the "show" method to be able to take in any reference to one of these 4 options and change its value from false to true.
in the show method, I get the error "'el' is defined but never used."
the only solution I've come to is to do an if method "if this.one === el{...}" but that kind of defeats the purpose. any help would be appreciated
You can do something like this:
export default {
data(){
return {
one: false,
two: false
}
},
methods: {
doSomething(el) {
this[el] = true;
}
},
mounted(){
//Also works with vue props!
this.doSomething('one')
this.doSomething('two')
}
}
But if the function is more complex you should build a componet for that. Thats the vue way.
new Vue({
el: '#editor',
data: {
el: false,
item : ''
},
computed: {
},
methods: {
show (passedValue, item) {
this.item = item
this.el = passedValue
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/marked#0.3.6"></script>
<script src="https://unpkg.com/lodash#4.16.0"></script>
<div id="editor">
<div id="div1" v-if="el===true && item =='div-1'" class="show">
div1 displayed
</div>
<div id="div-2" v-if="el===true && item =='div-2'" class="show">
div2 displayed
</div>
<button v-on:click="show(true, 'div-1')">show div1</button>
<button v-on:click="show(false, 'div-1')">hide div1</button>
<button v-on:click="show(true, 'div-2')">show div2</button>
<button v-on:click="show(false, 'div-2')">hide div2</button>
</div>
export default {
data(){
return {
one: false,
}
},
methods: {
show(bol) {
this.one = bol;
}
},
mounted(){
this.show(this.one)
}
}
Use above code it would work.
I have a simple component that uses mixin that's shared across multiple components with similar functionality.
When I run it I seem to be getting
Property or method "activeClass" is not defined on the instance but
referenced during render.
Here's my mixin
<script>
export default {
data() {
return {
opened: false,
identity: ''
}
},
computed: {
activeClass() {
return {
active: this.opened
};
}
},
created() {
window.EventHandler.listen(this.identity + '-toggled', opened => this.opened = opened);
},
methods: {
toggle() {
window.EventHandler.fire('toggle-' + this.identity);
}
}
}
</script>
and my component
<template>
<span class="pointer" :class="activeClass" #click="toggle"><i class="fas fa-search"></i></span>
</template>
<script>
import Trigger from '../../mixins/Trigger';
export default {
data() {
return {
mixins: [Trigger],
data() {
return {
identity: 'language'
}
}
}
}
}
</script>
For some reason I cannot seem to be able to access activeClass computed property from within the component. Any idea why is this happening?
Try to move mixin to components main scope. Not in data function rerurn
I'm a bit confused about how to change properties inside components, let's say I have the following component:
{
props: {
visible: {
type: Boolean,
default: true
}
},
methods: {
hide() {
this.visible = false;
}
}
}
Although it works, it would give the following warning:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "visible"
(found in component )
Now I'm wondering what the best way to handle this is, obviously the visible property is passed in when created the component in the DOM: <Foo :visible="false"></Foo>
Referencing the code in your fiddle
Somehow, you should decide on one place for the state to live, not two. I don't know whether it's more appropriate to have it just in the Alert or just in it's parent for your use case, but you should pick one.
How to decide where state lives
Does the parent or any sibling component depend on the state?
Yes: Then it should be in the parent (or in some external state management)
No: Then it's easier to have it in the state of the component itself
Kinda both: See below
In some rare cases, you may want a combination. Perhaps you want to give both parent and child the ability to hide the child. Then you should have state in both parent and child (so you don't have to edit the child's props inside child).
For example, child can be visible if: visible && state_visible, where visible comes from props and reflects a value in the parent's state, and state_visible is from the child's state.
I'm not sure if this is the behavour that you want, but here is a snippet. I would kinda assume you actually want to just call the toggleAlert of the parent component when you click on the child.
var Alert = Vue.component('alert', {
template: `
<div class="alert" v-if="visible && state_visible">
Alert<br>
<span v-on:click="close">Close me</span>
</div>`,
props: {
visible: {
required: true,
type: Boolean,
default: false
}
},
data: function() {
return {
state_visible: true
};
},
methods: {
close() {
console.log('Clock this');
this.state_visible = false;
}
}
});
var demo = new Vue({
el: '#demo',
components: {
'alert': Alert
},
data: {
hasAlerts: false
},
methods: {
toggleAlert() {
this.hasAlerts = !this.hasAlerts
}
}
})
.alert {
background-color: #ff0000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo" v-cloak>
<alert :visible="hasAlerts"></alert>
<span v-on:click="toggleAlert">Toggle alerts</span>
</div>
According to the Vue.js component doc:
When the parent property updates, it will flow down to the child, but not the other way around. So, how do we communicate back to the parent when something happens? This is where Vue’s custom event system comes in.
Use $emit('my-event) from the child to send an event to the parent. Receive the event on the child declaration inside the parent with v-on:my-event (or #my-event).
Working example:
// child
Vue.component('child', {
template: '<div><p>Child</p> <button #click="hide">Hide</button></div>',
methods: {
hide () {
this.$emit('child-hide-event')
}
},
})
// parent
new Vue({
el: '#app',
data: {
childVisible: true
},
methods: {
childHide () {
this.childVisible = false
},
childShow () {
this.childVisible = true
}
}
})
.box {
border: solid 1px grey;
padding: 16px;
}
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<div id="app" class="box">
<p>Parent | childVisible: {{ childVisible }}</p>
<button #click="childHide">Hide</button>
<button #click="childShow">Show</button>
<p> </p>
<child #child-hide-event="childHide" v-if="childVisible" class="box"></child>
</div>
If the prop is only useful for this child component, give the child a prop like initialVisible, and a data like mutableVisible, and in the created hook (which is called when the component's data structure is assembled), simply this.mutableVisible = this.initialVisible.
If the prop is shared by other children of the parent component, you'll need to make it the parent's data to make it available for all children. Then in the child, this.$emit('visibleChanged', currentVisible) to notify the parent to change visible. In parent's template, use <ThatChild ... :visibleChanged="setVisible" ...>. Take a look at the guide: https://v2.vuejs.org/v2/guide/components.html
After a read of your latest comments it seems that you are concerned about having the logic to show/hide the alerts on the parent. Therefore I would suggest the following:
parent
# template
<alert :alert-visible="alertVisible"></alert>
# script
data () {
alertVisible: false,
...
},
...
Then on the child alert you would $watch the value of the prop and move all logic into the alert:
child (alert)
# script
data: {
visible: false,
...
},
methods: {
hide () {
this.visible = false
},
show () {
this.visible = true
},
...
},
props: [
'alertVisible',
],
watch: {
alertVisible () {
if (this.alertVisible && !this.visible) this.show()
else if (!this.alertVisible && this.visible) this.hide()
},
...
},
...
To help anybody, I was facing the same issue. I just changed my var that was inside v-model="" from props array to data. Remember the difference between props and data, im my case that was not a problem changing it, you should weight your decision.
E.g.:
<v-dialog v-model="dialog" fullscreen hide-overlay transition="dialog-bottom-transition">
Before:
export default {
data: function () {
return {
any-vars: false
}
},
props: {
dialog: false,
notifications: false,
sound: false,
widgets: false
},
methods: {
open: function () {
var vm = this;
vm.dialog = true;
}
}
}
After:
export default {
data: function () {
return {
dialog: false
}
},
props: {
notifications: false,
sound: false,
widgets: false
},
methods: {
open: function () {
var vm = this;
vm.dialog = true;
}
}
}
Maybe it looks like on hack and violates the concept of a single data source, but its work)
This solution is creating local proxy variable and inherit data from props. Next work with proxy variable.
Vue.component("vote", {
data: function() {
return {
like_: this.like,
dislike_: this.dislike,
}
},
props: {
like: {
type: [String, Number],
default: 0
},
dislike: {
type: [String, Number],
default: 0
},
item: {
type: Object
}
},
template: '<div class="tm-voteing"><span class="tm-vote tm-vote-like" #click="onVote(item, \'like\')"><span class="fa tm-icon"></span><span class="tm-vote-count">{{like_}}</span></span><span class="tm-vote tm-vote-dislike" #click="onVote(item, \'dislike\')"><span class="fa tm-icon"></span><span class="tm-vote-count">{{dislike_}}</span></span></div>',
methods: {
onVote: function(data, action) {
var $this = this;
// instead of jquery ajax can be axios or vue-resource
$.ajax({
method: "POST",
url: "/api/vote/vote",
data: {id: data.id, action: action},
success: function(response) {
if(response.status === "insert") {
$this[action + "_"] = Number($this[action + "_"]) + 1;
} else {
$this[action + "_"] = Number($this[action + "_"]) - 1;
}
},
error: function(response) {
console.error(response);
}
});
}
}
});
use component and pass props
<vote :like="item.vote_like" :dislike="item.vote_dislike" :item="item"></vote>
I wonder why it is missed by others when the warning has a hint
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "visible" (found in component )
Try creating a computed property out of the prop received in the child component as
computed: {
isVisible => this.visible
}
And use this computed in your child component as well as to emit the changes to your parent.