Use computed property in data in Vuejs - vue.js

How can I use a computed property in the data or emit it via bus?
I have the following vue instance, but myComputed is always undefined but computedData is working correctly.
var vm = new Vue({
data(){
return{
myComputed: this.computedData
}
},
computed: {
computedData(){
return 'Hello World'
}
}
})

Unfortunately, it is impossible to use computed property in data because of component creation timing: data evaluates Before computed properties.

To make things as simple as possible, just do the work in watcher, unless you want to emit the changes to different components or there're a lot of variables you want to notify, then you may have to use Vuex or the event bus:
var vm = new Vue({
data(){
return{
myComputed: '',
computedData: 'Hello World'
}
},
created() {
this.myComputed = this.computedData;
},
watch: {
computedData() {
this.myComputed = this.computedData;
}
}
});

Computed is already accessible in the template using {{ }}.
But you can use the
watch:{
//your function here
}
instead of computed

If you are using computed/reactive objects then it should be inside the computed and not inside the data.
Simply change your code to use computed instead of data
var vm = new Vue({
data(){
return{}
},
computed: {
computedData(){
return 'Hello World'
},
myComputed(){
return this.computedData
}
}
})
you are trying to use data as computed and this shall not be.
data doesn't act like computed object.
and it's not because of component creation timing. What if we changed the component creation timing ? this will not solve anything as data will take only the first computed value(only one) and will not update after.

you can work just with the computed function
var vm = new Vue({
data(){
return{
//is not necessary
}
},
computed: {
computedData(){
return 'Hello World'
}
}
})
and in your template
<template>
<div>{{ computedData }}</div>
</template>

You are over-coding it. Computed props are accessible in the same manner as data props in your template.
var vm = new Vue({
computed: {
myComputed(){
return 'Hello World'
}
}
})
In the template you have access to this just like you do to data:
<template>
<div>{{ myComputed }}</div>
</template>
https://v2.vuejs.org/v2/guide/computed.html

Try to convert the computed in a method
var vm = new Vue({
data(){
return{
myComputed: this.computedData
}
},
methods: {
computedData(){
return 'Hello World'
}
}
})
This is simple and it works (NOT reactive), but has a cost:
https://medium.com/notonlycss/the-difference-between-computed-and-methods-in-vue-js-9cb05c59ed98

computed is not available at the time data gets initialized.
If it should be a one-time thing (and NOT reactive), you could achieve this by setting the data at the moment where the computed property is available by using the created() hook:
export default {
data: () => ({
myDataBackend: '',
}),
computed: {
computedData () {
return 'Hello World'
}
},
created() {
this.$set(this, 'myDataBackend', this.computedData)
}
}
Futher reading: Vue Documentation on Lifecycle Hooks
In case you are trying to work with v-model:
You could also use :value and some event like #change or #keyup in the element instead.
:value is the value which the input-element initially works with
After writing some letter in the input field, the #keyup event changes the data.
Typically, events carry the updated form value in target.value
The changeMyData method sets the value
the computed property listens to the data change and the :value of the input field gets updated.
Note: I used data as a data store. But you could also use for example vuex instead.
<template>
<div>
<input
type="text"
:value="computedData"
#keyup="changeMyData"
/>
<p>{{myDataBackend}}</p>
</div>
</template>
<script>
export default {
data: () => ({
myDataBackend: 'Hello World'
}),
methods: {
changeMyData(evt) {
this.$set(this, 'myDataBackend', evt.target.value)
console.log('Changed the value to: ' + evt.target.value)
}
},
computed: {
computedData () {
return this.myDataBackend
}
}
}
</script>

Related

Unexpected mutation of prop in Vue2 [duplicate]

I started https://laracasts.com/series/learning-vue-step-by-step series. I stopped on the lesson Vue, Laravel, and AJAX with this error:
vue.js:2574 [Vue warn]: 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: "list" (found in component )
I have this code in main.js
Vue.component('task', {
template: '#task-template',
props: ['list'],
created() {
this.list = JSON.parse(this.list);
}
});
new Vue({
el: '.container'
})
I know that the problem is in created() when I overwrite the list prop, but I am a newbie in Vue, so I totally don't know how to fix it. Does anyone know how (and please explain why) to fix it?
This has to do with the fact that mutating a prop locally is considered an anti-pattern in Vue 2
What you should do now, in case you want to mutate a prop locally, is to declare a field in your data that uses the props value as its initial value and then mutate the copy:
Vue.component('task', {
template: '#task-template',
props: ['list'],
data: function () {
return {
mutableList: JSON.parse(this.list);
}
}
});
You can read more about this on Vue.js official guide
Note 1: Please note that you should not use the same name for your prop and data, i.e.:
data: function () { return { list: JSON.parse(this.list) } } // WRONG!!
Note 2: Since I feel there is some confusion regarding props and reactivity, I suggest you to have a look on this thread
The Vue pattern is props down and events up. It sounds simple, but is easy to forget when writing a custom component.
As of Vue 2.2.0 you can use v-model (with computed properties). I have found this combination creates a simple, clean, and consistent interface between components:
Any props passed to your component remains reactive (i.e., it's not cloned nor does it require a watch function to update a local copy when changes are detected).
Changes are automatically emitted to the parent.
Can be used with multiple levels of components.
A computed property permits the setter and getter to be separately defined. This allows the Task component to be rewritten as follows:
Vue.component('Task', {
template: '#task-template',
props: ['list'],
model: {
prop: 'list',
event: 'listchange'
},
computed: {
listLocal: {
get: function() {
return this.list
},
set: function(value) {
this.$emit('listchange', value)
}
}
}
})
The model property defines which prop is associated with v-model, and which event will be emitted on changes. You can then call this component from the parent as follows:
<Task v-model="parentList"></Task>
The listLocal computed property provides a simple getter and setter interface within the component (think of it like being a private variable). Within #task-template you can render listLocal and it will remain reactive (i.e., if parentList changes it will update the Task component). You can also mutate listLocal by calling the setter (e.g., this.listLocal = newList) and it will emit the change to the parent.
What's great about this pattern is that you can pass listLocal to a child component of Task (using v-model), and changes from the child component will propagate to the top level component.
For example, say we have a separate EditTask component for doing some type of modification to the task data. By using the same v-model and computed properties pattern we can pass listLocal to the component (using v-model):
<script type="text/x-template" id="task-template">
<div>
<EditTask v-model="listLocal"></EditTask>
</div>
</script>
If EditTask emits a change it will appropriately call set() on listLocal and thereby propagate the event to the top level. Similarly, the EditTask component could also call other child components (such as form elements) using v-model.
Vue just warns you: you change the prop in the component, but when parent component re-renders, "list" will be overwritten and you lose all your changes. So it is dangerous to do so.
Use computed property instead like this:
Vue.component('task', {
template: '#task-template',
props: ['list'],
computed: {
listJson: function(){
return JSON.parse(this.list);
}
}
});
If you're using Lodash, you can clone the prop before returning it. This pattern is helpful if you modify that prop on both the parent and child.
Let's say we have prop list on component grid.
In Parent Component
<grid :list.sync="list"></grid>
In Child Component
props: ['list'],
methods:{
doSomethingOnClick(entry){
let modifiedList = _.clone(this.list)
modifiedList = _.uniq(modifiedList) // Removes duplicates
this.$emit('update:list', modifiedList)
}
}
Props down, events up. That's Vue's Pattern. The point is that if you try to mutate props passing from a parent. It won't work and it just gets overwritten repeatedly by the parent component. Child component can only emit an event to notify parent component to do sth. If you don't like these restrict, you can use VUEX(actually this pattern will suck in complex components structure, you should use VUEX!)
You should not change the props's value in child component.
If you really need to change it you can use .sync.
Just like this
<your-component :list.sync="list"></your-component>
Vue.component('task', {
template: '#task-template',
props: ['list'],
created() {
this.$emit('update:list', JSON.parse(this.list))
}
});
new Vue({
el: '.container'
})
According to the VueJs 2.0, you should not mutate a prop inside the component. They are only mutated by their parents. Therefore, you should define variables in data with different names and keep them updated by watching actual props.
In case the list prop is changed by a parent, you can parse it and assign it to mutableList. Here is a complete solution.
Vue.component('task', {
template: ´<ul>
<li v-for="item in mutableList">
{{item.name}}
</li>
</ul>´,
props: ['list'],
data: function () {
return {
mutableList = JSON.parse(this.list);
}
},
watch:{
list: function(){
this.mutableList = JSON.parse(this.list);
}
}
});
It uses mutableList to render your template, thus you keep your list prop safe in the component.
The answer is simple, you should break the direct prop mutation by assigning the value to some local component variables(could be data property, computed with getters, setters, or watchers).
Here's a simple solution using the watcher.
<template>
<input
v-model="input"
#input="updateInput"
#change="updateInput"
/>
</template>
<script>
export default {
props: {
value: {
type: String,
default: '',
},
},
data() {
return {
input: '',
};
},
watch: {
value: {
handler(after) {
this.input = after;
},
immediate: true,
},
},
methods: {
updateInput() {
this.$emit('input', this.input);
},
},
};
</script>
It's what I use to create any data input components and it works just fine. Any new data sent(v-model(ed)) from parent will be watched by the value watcher and is assigned to the input variable and once the input is received, we can catch that action and emit input to parent suggesting that data is input from the form element.
do not change the props directly in components.if you need change it set a new property like this:
data() {
return {
listClone: this.list
}
}
And change the value of listClone.
I faced this issue as well. The warning gone after i use $on and $emit.
It's something like use $on and $emit recommended to sent data from child component to parent component.
one-way Data Flow,
according to https://v2.vuejs.org/v2/guide/components.html, the component follow one-Way
Data Flow,
All props form a one-way-down binding between the child property and the parent one, when the parent property updates, it will flow down to the child but not the other way around, this prevents child components from accidentally mutating the parent's, which can make your app's data flow harder to understand.
In addition, every time the parent component is updates all props
in the child components will be refreshed with the latest value. This means you should not attempt to mutate a prop inside a child component. If you do .vue will warn you in the
console.
There are usually two cases where it’s tempting to mutate a prop:
The prop is used to pass in an initial value; the child component wants to use it as a local data property afterwards.
The prop is passed in as a raw value that needs to be transformed.
The proper answer to these use cases are:
Define a local data property that uses the prop’s initial value as its initial value:
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
Define a computed property that is computed from the prop’s value:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
If you want to mutate props - use object.
<component :model="global.price"></component>
component:
props: ['model'],
methods: {
changeValue: function() {
this.model.value = "new value";
}
}
I want to give this answer which helps avoid using a lot of code, watchers and computed properties. In some cases this can be a good solution:
Props are designed to provide one-way communication.
When you have a modal show/hide button with a prop the best solution to me is to emit an event:
<button #click="$emit('close')">Close Modal</button>
Then add listener to modal element:
<modal :show="show" #close="show = false"></modal>
(In this case the prop show is probably unnecessary because you can use an easy v-if="show" directly on the base-modal)
You need to add computed method like this
component.vue
props: ['list'],
computed: {
listJson: function(){
return JSON.parse(this.list);
}
}
Vue.component('task', {
template: '#task-template',
props: ['list'],
computed: {
middleData() {
return this.list
}
},
watch: {
list(newVal, oldVal) {
console.log(newVal)
this.newList = newVal
}
},
data() {
return {
newList: {}
}
}
});
new Vue({
el: '.container'
})
Maybe this will meet your needs.
Vue3 has a really good solution. Spent hours to reach there. But it worked really good.
On parent template
<user-name
v-model:first-name="firstName"
v-model:last-name="lastName"
></user-name>
The child component
app.component('user-name', {
props: {
firstName: String,
lastName: String
},
template: `
<input
type="text"
:value="firstName"
#input="$emit('update:firstName',
$event.target.value)">
<input
type="text"
:value="lastName"
#input="$emit('update:lastName',
$event.target.value)">
`
})
This was the only solution which did two way binding. I like that first two answers were addressing in good way to use SYNC and Emitting update events, and compute property getter setter, but that was heck of a Job to do and I did not like to work so hard.
Vue.js props are not to be mutated as this is considered an Anti-Pattern in Vue.
The approach you will need to take is creating a data property on your component that references the original prop property of list
props: ['list'],
data: () {
return {
parsedList: JSON.parse(this.list)
}
}
Now your list structure that is passed to the component is referenced and mutated via the data property of your component :-)
If you wish to do more than just parse your list property then make use of the Vue component' computed property.
This allow you to make more in depth mutations to your props.
props: ['list'],
computed: {
filteredJSONList: () => {
let parsedList = JSON.parse(this.list)
let filteredList = parsedList.filter(listItem => listItem.active)
console.log(filteredList)
return filteredList
}
}
The example above parses your list prop and filters it down to only active list-tems, logs it out for schnitts and giggles and returns it.
note: both data & computed properties are referenced in the template the same e.g
<pre>{{parsedList}}</pre>
<pre>{{filteredJSONList}}</pre>
It can be easy to think that a computed property (being a method) needs to be called... it doesn't
For when TypeScript is your preferred lang. of development
<template>
<span class="someClassName">
{{feesInLocale}}
</span>
</template>
#Prop({default: 0}) fees: any;
// computed are declared with get before a function
get feesInLocale() {
return this.fees;
}
and not
<template>
<span class="someClassName">
{{feesInLocale}}
</span>
</template>
#Prop() fees: any = 0;
get feesInLocale() {
return this.fees;
}
Assign the props to new variable.
data () {
return {
listClone: this.list
}
}
Adding to the best answer,
Vue.component('task', {
template: '#task-template',
props: ['list'],
data: function () {
return {
mutableList: JSON.parse(this.list);
}
}
});
Setting props by an array is meant for dev/prototyping, in production make sure to set prop types(https://v2.vuejs.org/v2/guide/components-props.html) and set a default value in case the prop has not been populated by the parent, as so.
Vue.component('task', {
template: '#task-template',
props: {
list: {
type: String,
default() {
return '{}'
}
}
},
data: function () {
return {
mutableList: JSON.parse(this.list);
}
}
});
This way you atleast get an empty object in mutableList instead of a JSON.parse error if it is undefined.
YES!, mutating attributes in vue2 is an anti-pattern. BUT...
Just break the rules by using other rules, and go forward!
What you need is to add .sync modifier to your component attribute in the parent scope.
<your-awesome-components :custom-attribute-as-prob.sync="value" />
Below is a snack bar component, when I give the snackbar variable directly into v-model like this if will work but in the console, it will give an error as
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.
<template>
<v-snackbar v-model="snackbar">
{{ text }}
</v-snackbar>
</template>
<script>
export default {
name: "loader",
props: {
snackbar: {type: Boolean, required: true},
text: {type: String, required: false, default: ""},
},
}
</script>
Correct Way to get rid of this mutation error is use watcher
<template>
<v-snackbar v-model="snackbarData">
{{ text }}
</v-snackbar>
</template>
<script>
/* eslint-disable */
export default {
name: "loader",
data: () => ({
snackbarData:false,
}),
props: {
snackbar: {type: Boolean, required: true},
text: {type: String, required: false, default: ""},
},
watch: {
snackbar: function(newVal, oldVal) {
this.snackbarData=!this.snackbarDatanewVal;
}
}
}
</script>
So in the main component where you will load this snack bar you can just do this code
<loader :snackbar="snackbarFlag" :text="snackText"></loader>
This Worked for me
Vue.js considers this an anti-pattern. For example, declaring and setting some props like
this.propsVal = 'new Props Value'
So to solve this issue you have to take in a value from the props to the data or the computed property of a Vue instance, like this:
props: ['propsVal'],
data: function() {
return {
propVal: this.propsVal
};
},
methods: {
...
}
This will definitely work.
In addition to the above, for others having the following issue:
"If the props value is not required and thus not always returned, the passed data would return undefined (instead of empty)". Which could mess <select> default value, I solved it by checking if the value is set in beforeMount() (and set it if not) as follows:
JS:
export default {
name: 'user_register',
data: () => ({
oldDobMonthMutated: this.oldDobMonth,
}),
props: [
'oldDobMonth',
'dobMonths', //Used for the select loop
],
beforeMount() {
if (!this.oldDobMonth) {
this.oldDobMonthMutated = '';
} else {
this.oldDobMonthMutated = this.oldDobMonth
}
}
}
Html:
<select v-model="oldDobMonthMutated" id="dob_months" name="dob_month">
<option selected="selected" disabled="disabled" hidden="hidden" value="">
Select Month
</option>
<option v-for="dobMonth in dobMonths"
:key="dobMonth.dob_month_slug"
:value="dobMonth.dob_month_slug">
{{ dobMonth.dob_month_name }}
</option>
</select>
I personally always suggest if you are in need to mutate the props, first pass them to computed property and return from there, thereafter one can mutate the props easily, even at that you can track the prop mutation , if those are being mutated from another component too or we can you watch also .
Because Vue props is one way data flow, This prevents child components from accidentally mutating the parent’s state.
From the official Vue document, we will find 2 ways to solve this problems
if child component want use props as local data, it is best to define a local data property.
props: ['list'],
data: function() {
return {
localList: JSON.parse(this.list);
}
}
The prop is passed in as a raw value that needs to be transformed. In this case, it’s best to define a computed property using the prop’s value:
props: ['list'],
computed: {
localList: function() {
return JSON.parse(this.list);
},
//eg: if you want to filter this list
validList: function() {
return this.list.filter(product => product.isValid === true)
}
//...whatever to transform the list
}
You should always avoid mutating props in vue, or any other framework. The approach you could take is copy it into another variable.
for example.
// instead of replacing the value of this.list use a different variable
this.new_data_variable = JSON.parse(this.list)
A potential solution to this is using global variables.
import { Vue } from "nuxt-property-decorator";
export const globalStore = new Vue({
data: {
list: [],
},
}
export function setupGlobalsStore() {
Vue.prototype.$globals = globalStore;
}
Then you would use:
$globals.list
Anywhere you need to mutate it or present it.

Vue child component not rerending after parent component's data value updates

I am trying to have a child component update its props that were passed from the parents at the start of the rendering. Since the value is coming from a fetch call, it takes a bit of time to get the value, so I understand that the child component will receive a 'null' variable. But once the fetch call is completed, the value is updated but the child component still has the original null value.
During my search for a solution, I found that another way was to use Vuex Stores, so I implemented it with the count variable and had a button to call a commit and later dispatch with an action function to the store to increment it's value but when the increment happens, it doesn't show the new value on the screen even though with console logs I confirmed it did change the value when the function was called.
I guess I don't fully understand how to update the value of a variable without reassigning it within it's own component or having to call a separate function manually right after I change the value of a data variable.
App.vue
<template>
<div id="app">
<div id="banner">
<div>Title</div>
</div>
<p>count: {{count}}</p> // a small test i was doing to figure out how to update data values
<button #click="update">Click </button>
<div id="content" class="container">
<CustomDropdown title="Title Test" :valueProps="values" /> // passing the data into child component
</div>
</div>
</template>
<script>
import CustomDropdown from './components/CustomDropdown.vue'
export default {
name: 'App',
components: {
CustomDropdown,
},
data() {
return {
values: null
count: this.$store.state.count
}
},
methods: {
update() {
this.$store.dispatch('increment')
}
},
async created() {
const response = await fetch("http://localhost:3000/getIds", {
method: 'GET',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}
});
const data = await response.json();
this.values = data // This is when I expect the child component to rerender and show the new data. data is an array of objects
console.log("data", data, this.values) // the console log shows both variables have data
}
}
</script>
CustomDropDown.vue
<template>
<div id="dropdown-container" class="">
<b-dropdown class="outline danger" variant="outline-dark" :text="title" :disabled="disabled">
<b-dropdown-item
v-for="value in values"
:key="value.DIV_ID"
href="#">
{{value.name}}
</b-dropdown-item>
</b-dropdown>
</div>
</template>
<script>
export default {
name: 'CustomDropdown',
components: {},
props: {
title: String,
valuesProp: Array,
disabled: Boolean
},
data() {
return {
values: this.valuesProp
}
},
methods: {
},
created() {
console.log("dropdown created")
console.log(this.valuesProp) //Always undefined
}
}
</script>
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state() {
return {
count: 0,
divisionIds: []
}
},
mutations: {
increment (state) {
console.log("count", state.count)
state.count++
}
},
actions: {
increment (state) {
console.log("count action", state.count)
state.commit('increment')
}
}
})
data in your child component CustomDropdown.vue is not reactive: therefore the value of this.values is not updated when the prop changes. If you want to alias a prop, use computed instead:
export default {
name: 'CustomDropdown',
components: {},
props: {
title: String,
valuesProp: Array,
disabled: Boolean
},
computed: {
values() {
return this.valuesProp;
}
},
created() {
console.log("dropdown created");
}
}
If you want to console log the most updated values of this.valuesProp, you will need to watch it: the same if you want for this.values.
One thing you can do is to use a v-if in your child component to only render it after you get your result from you api.
It would be something like:
<CustomDropdown title="Title Test" :valueProps="values" v-if="values"/>
This way you would make sure that your child component gets rendered only when values are available.
It would only be a bad solution if this api call took so long and you needed to display the child component data to the user before that.
Hey you can simply watch it your child component
watch: { valuesProp: function(newVal, oldVal) { // watch it if(newVal.length > 0) do something }
it will watch for the value changes and when you get your desired value you can perform whatever hope it will help you you dont need store or conditional binding for it.

How watch global variable in component vuejs?

I need global variables for errors. But I don't want set input variable for every component.
How I can watch $errors in component ABC without input variable?
(without <abc :errors="$errors"></abc>)
index.js:
Vue.prototype.$errors = {};
new Vue({
el: '#app',
render: h => h(App),
}
App.vue:
...
name: 'App',
components: {
ABC
}
...
methods:{
getContent() {
this.$errors = ...from axis...
}
Component ABC:
<template>
<div>{{ error }}</div>
</template>
...
watch: {
???
}
Here's an example of how it could be done:
const errors = Vue.observable({ errors: {} })
Object.defineProperty(Vue.prototype, '$errors', {
get () {
return errors.errors
},
set (value) {
errors.errors = value
}
})
new Vue({
el: '#app',
methods: {
newErrors () {
// Generate some random errors
const errors = {}
for (const property of ['name', 'type', 'id']) {
if (Math.random() < 0.5) {
errors[property] = 'Invalid value'
}
}
this.$errors = errors
}
}
})
new Vue({
el: '#app2',
watch: {
$errors () {
console.log('$errors has changed')
}
}
});
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<pre>{{ $errors }}</pre>
<button #click="newErrors">New errors</button>
</div>
<div id="app2">
<pre>{{ $errors }}</pre>
</div>
I've created two Vue instances to illustrate that the value really is shared. Clicking the button in the first instance will update the value of $errors and the watch is triggered in the second instance.
There are a few tricks in play here.
Firstly, reactivity can only track the reading and writing of properties of an observable object. So the first thing we do is create a suitable object:
const errors = Vue.observable({ errors: {} })
We then need to wire this up to Vue.prototype.$errors. By defining a get and set for that property we can proxy through to the underlying property within our observable object.
All of this is pretty close to how data properties work behind the scenes. For the data properties the observable object is called $data. Vue then uses defineProperty with get and set to proxy though from the Vue instance to the $data object, just like in my example.
as Estradiaz said:
You can use Vuex and access the value outside of Vue like in this answer: https://stackoverflow.com/a/47575742/10219239
This is an addition to Skirtles answer:
You can access such variables via Vue.prototype.variable.
You can set them directly, or use Vue.set, it works either way.
My code (basically the same as Skirtless):
main.js
const mobile = Vue.observable({ mobile: {} });
Object.defineProperty(Vue.prototype, '$mobile', {
get() { return mobile.mobile; },
set(value) { mobile.mobile = value; }
});
function widthChanged() {
if (window.innerWidth <= 768) {
if (!Vue.prototype.$mobile) Vue.set(Vue.prototype, '$mobile', true);
} else if (Vue.prototype.$mobile) Vue.set(Vue.prototype, '$mobile', false);
}
window.addEventListener("resize", widthChanged);
widthChanged();
Home.vue:
watch: {
'$mobile'(newValue) {
// react to Change in width
}
}

Using Event Names stored in a variable in VueJs

In VueJS, a child component can emit an event, for example:
this.$emit('toggle-button')
In the parent, we can listen to this event as follows:
<my-component v-on:toggle-button="doSomething"></my-component>
This works great. But I have a requirement where the event name (in this case, toggle-button) is stored in a variable or Vuex store. So, I don't have the exact event name but a variable or store with the name of the event.
In such a case what would be the syntax for referring that that event name in the on-click directive?
For example, let say we have:
let eventName = 'toggle-button'
How can I use this variable (eventName) instead of the exact event name (toggle-button) in the following:
<my-component v-on:toggle-button="doSomething"></my-component>
You could use $on(EVENT_NAME, CALLBACK) in this case:
// <my-component ref="foo" />
this.$refs.foo.$on(eventName, doSomething)
Vue.component('my-component', {
template: `<button #click="$emit('click', $event)">Click</button>`
});
new Vue({
el: '#app',
mounted() {
const eventName = 'click';
this.$refs.foo.$on(eventName, this.doSomething);
},
methods: {
doSomething() {
alert('clicked');
}
}
})
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<my-component ref="foo" />
</div>
If you render your component using separate render function, this is easy:
export default {
props: {
eventName: {
type: String,
},
},
render: function (createElement) {
return createElement('my-component', {
on: {
[this.eventName]: (event) => {
console.log('Received event!')
}
}
})
}
}
Render functions also come with ability to dynamically specify a name for the component, if that is also required for your app

Vue.js 2.3 - Element UI Dialog - Sync and Props

I'm using vue-js 2.3 and element-ui. Since the update 2.3 of vue-js and the reintroduction of sync, things have changed and I have had a hard time figuring out my problem.
Here is the jsfiddle : https://jsfiddle.net/7o5z7dc1/
Problem
The dialog is only opened once. When I close it I have this error:
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:
"showDialog"
Questions
What am I doing wrong?
How can I fix the current situation?
EDIT
If I'm creating a data I manage to remove the error message but the dialog does not close anymore
<div id="app">
<left-panel v-on:show="showDialog = true">></left-panel>
<popup v-if="showDialog":show-dialog.sync="showDialog"></popup>
</div>
<template id="left-panel-template">
<button #click="$emit('show')">Show Component PopUp</button>
</template>
<template id="popup">
<el-dialog :visible.sync="showDialog" #visiblechange="updateShowDialog">I am the popup</el-dialog>
</template>
Vue.component('left-panel', {
template: '#left-panel-template',
methods: {
},
});
Vue.component('popup', {
template: '#popup',
props : ['showDialog'],
methods: {
updateShowDialog(isVisible) {
if (isVisible) return false;
this.$emit('update:showDialog', false )
},
},
});
var vm = new Vue({
el: '#app',
data: {
showDialog: false,
},
methods: {
}
});
You avoid the mutation warning by making a copy of the prop and mutating that instead of mutating the property directly.
Vue.component('popup', {
template: '#popup',
props : ['showDialog'],
data(){
return {
show: this.showDialog
}
},
methods: {
updateShowDialog(isVisible) {
if (isVisible) return false;
this.$emit('update:showDialog', false )
},
},
});
I also changed your template to handle the visible-change event correctly.
<el-dialog :visible.sync="show" #visible-change="updateShowDialog">I am the popup</el-dialog>
Updated fiddle.