Vue v-on:click does not work on component
JavascriptVue ComponentVuejs2vue.jsJavascript Problem Overview
I'm trying to use the on click directive inside a component but it does not seem to work. When I click the component nothings happens when I should get a 'test clicked' in the console. I don't see any errors in the console, so I don't know what am I doing wrong.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>vuetest</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
App.vue
<template>
<div id="app">
<test v-on:click="testFunction"></test>
</div>
</template>
<script>
import Test from './components/Test'
export default {
name: 'app',
methods: {
testFunction: function (event) {
console.log('test clicked')
}
},
components: {
Test
}
}
</script>
Test.vue (the component)
<template>
<div>
click here
</div>
</template>
<script>
export default {
name: 'test',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
Javascript Solutions
Solution 1 - Javascript
If you want to listen to a native event on the root element of a component, you have to use the .native modifier for v-on
, like following:
<template>
<div id="app">
<test v-on:click.native="testFunction"></test>
</div>
</template>
or in shorthand, as suggested in comment, you can as well do:
<template>
<div id="app">
<test @click.native="testFunction"></test>
</div>
</template>
Solution 2 - Javascript
I think the $emit
function works better for what I think you're asking for. It keeps your component separated from the Vue instance so that it is reusable in many contexts.
// Child component
<template>
<div id="app">
<test @click="$emit('test-click')"></test>
</div>
</template>
Use it in HTML
// Parent component
<test @test-click="testFunction">
Solution 3 - Javascript
It's the @Neps' answer but with details.
Note: @Saurabh's answer is more suitable if you don't want to modify your component or don't have access to it.
Why can't @click just work?
Components are complicated. One component can be a small fancy button wrapper, and another one can be an entire table with bunch of logic inside. Vue doesn't know what exactly you expect when bind v-model
or use v-on
so all of that should be processed by component's creator.
How to handle click event
According to Vue docs, $emit
passes events to parent. Example from docs:
Main file
<blog-post
@enlarge-text="onEnlargeText"
/>
Component
<button @click="$emit('enlarge-text')">
Enlarge text
</button>
(@
is the v-on
shorthand)
Component handles native click
event and emits parent's @enlarge-text="..."
enlarge-text
can be replaced with click
to make it look like we're handling a native click event:
<blog-post
@click="onEnlargeText"
></blog-post>
<button @click="$emit('click')">
Enlarge text
</button>
But that's not all. $emit
allows to pass a specific value with an event. In the case of native click
, the value is MouseEvent (JS event that has nothing to do with Vue).
Vue stores that event in a $event
variable. So, it'd the best to emit $event
with an event to create the impression of native event usage:
<button v-on:click="$emit('click', $event)">
Enlarge text
</button>
Solution 4 - Javascript
As mentioned by Chris Fritz (Vue.js Core Team Emeriti) in VueCONF US 2019
> If we had Kia enter .native
and then the root element of the base input changed from an input to a label suddenly this component is broken and it's not obvious and in fact, you might not even catch it right away unless you have a really good test. Instead by avoiding the use of the .native
modifier which I currently consider an anti-pattern, and will be removed in Vue 3, you'll be able to explicitly define that the parent might care about which element listeners are added to...
With Vue 2
$listeners
:
Using So, if you are using Vue 2, a better option to resolve this issue would be to use a fully transparent wrapper logic. For this, Vue provides a $listeners
property containing an object of listeners being used on the component. For example:
{
focus: function (event) { /* ... */ }
input: function (value) { /* ... */ },
}
and then we just need to add v-on="$listeners"
to the test
component like:
Test.vue (child component)
<template>
<div v-on="$listeners">
click here
</div>
</template>
Now the <test>
component is a fully transparent wrapper, meaning it can be used exactly like a normal <div>
element: all the listeners will work, without the .native
modifier.
Demo:
Vue.component('test', {
template: `
<div class="child" v-on="$listeners">
Click here
</div>`
})
new Vue({
el: "#myApp",
data: {},
methods: {
testFunction: function(event) {
console.log('test clicked')
}
}
})
div.child{border:5px dotted orange; padding:20px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="myApp">
<test @click="testFunction"></test>
</div>
$emit
method:
Using We can also use the $emit
method for this purpose, which helps us to listen to a child component's events in the parent component. For this, we first need to emit a custom event from a child component, like:
Test.vue (child component)
<test @click="$emit('my-event')"></test>
Important: Always use kebab-case for event names. For more information and a demo regading this point please check out this answer: VueJS passing computed value from component to parent.
Now, we just need to listen to this emitted custom event in the parent component, like:
App.vue
<test @my-event="testFunction"></test>
So basically, instead of v-on:click
or the shorthand @click
we will simply use v-on:my-event
or just @my-event
.
Demo:
Vue.component('test', {
template: `
<div class="child" @click="$emit('my-event')">
Click here
</div>`
})
new Vue({
el: "#myApp",
data: {},
methods: {
testFunction: function(event) {
console.log('test clicked')
}
}
})
div.child{border:5px dotted orange; padding:20px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="myApp">
<test @my-event="testFunction"></test>
</div>
With Vue 3
v-bind="$attrs"
:
Using Vue 3 is going to make our life much easier in many ways. One example is that it will help us create a simpler transparent wrapper with less config, by just using v-bind="$attrs"
. By using this on child components, not only will our listener work directly from the parent, but also any other attributes will also work just like they would with a normal <div>
.
So, with respect to this question, we will not need to update anything in Vue 3 and your code will still work fine, as <div>
is the root element here and it will automatically listen to all child events.
Demo #1:
const { createApp } = Vue;
const Test = {
template: `
<div class="child">
Click here
</div>`
};
const App = {
components: { Test },
setup() {
const testFunction = event => {
console.log("test clicked");
};
return { testFunction };
}
};
createApp(App).mount("#myApp");
div.child{border:5px dotted orange; padding:20px;}
<script src="//unpkg.com/vue@next"></script>
<div id="myApp">
<test v-on:click="testFunction"></test>
</div>
But, for complex components with nested elements where we need to apply attributes and events to the <input />
instead of the parent label we can simply use v-bind="$attrs"
Demo #2:
const { createApp } = Vue;
const BaseInput = {
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input v-bind="$attrs">
</label>`
};
const App = {
components: { BaseInput },
setup() {
const search = event => {
console.clear();
console.log("Searching...", event.target.value);
};
return { search };
}
};
createApp(App).mount("#myApp");
input{padding:8px;}
<script src="//unpkg.com/vue@next"></script>
<div id="myApp">
<base-input
label="Search: "
placeholder="Search"
@keyup="search">
</base-input><br/>
</div>
Solution 5 - Javascript
A bit verbose but this is how I do it:
@click="$emit('click', $event)"
UPDATE: Example added by @sparkyspider
<div-container @click="doSomething"></div-container>
In div-container
component...
<template>
<div @click="$emit('click', $event);">The inner div</div>
</template>
Solution 6 - Javascript
Native events of components aren't directly accessible from parent elements. Instead you should try v-on:click.native="testFunction"
, or you can emit an event from Test
component as well. Like v-on:click="$emit('click')"
.
Solution 7 - Javascript
One use case of using @click.native
is when you create a custom component and you want to listen to click event on the custom component. For example:
#CustomComponent.vue
<div>
<span>This is a custom component</span>
</div>
#App.vue
<custom-component @click.native="onClick"></custom-component>
@click.native
always work for this situation.
Solution 8 - Javascript
From the documentation:
Due to limitations in JavaScript, Vue cannot detect the following changes to an array:
- When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
- When you modify the length of the array, e.g. vm.items.length = newLength
In my case i stumbled on this problem when migrating from Angular to VUE. Fix was quite easy, but really difficult to find:
setValue(index) {
Vue.set(this.arr, index, !this.arr[index]);
this.$forceUpdate(); // Needed to force view rerendering
}
Solution 9 - Javascript
App.vue
<div id="app">
<test @itemClicked="testFunction($event)"/>
</div>
Test.vue
<div @click="$emit('itemClicked', data)">
click here
</div>