308

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>
Saurabh
  • 71,488
  • 40
  • 181
  • 244
Javier Cárdenas
  • 3,935
  • 4
  • 25
  • 35

9 Answers9

526

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>

Reference to read more about native event

tony19
  • 125,647
  • 18
  • 229
  • 307
Saurabh
  • 71,488
  • 40
  • 181
  • 244
  • 8
    Or the shorthand `@click.native="testFunction"` – Pier Apr 16 '17 at 21:37
  • 109
    what is a **native event** or how is it different from other *normal* events ? Why this special case for root elements ??? – MrClan Jul 22 '17 at 03:09
  • 27
    @MrClan https://vuejs.org/v2/guide/migration.html#Listening-for-Native-Events-on-Components-with-v-on-changed – user2875289 Jul 26 '17 at 09:03
  • 12
    native modifier is advised against to be used in vue. Use it only if absolutely necessary. See @jim-mcneely for the correct way to achieve this i.e. emitting events from the child element and then reviving it in the parent. – Deiknymi Jun 13 '18 at 08:17
  • 1
    @Dimensionless Why is it advised against? Can you please provide a link or elaborate? – WoodrowShigeru Aug 09 '18 at 09:32
  • 3
    When `v-on` is used on an HTML element, it's basically `addEventListener` for native HTML element events because this is the only kind of events these elements have. But when used on a Vue component, it's instead a listener to Vue events, `$emit`-ted from this component, like `this.$emit('click')` or `this.$emit('whateverEventNameYouUse')`. (_this was intended to be the edit for the answer but got rejected_) – stsloth Aug 12 '18 at 17:21
  • 7
    Here's the correct link to [native events](https://vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components) – Alexander Kim Aug 25 '18 at 11:01
  • My testFunction executes when chrome developer options is open. If I close the developer tools, the method does not get called. Any thoughts on this? – Balaji Kartheeswaran Feb 03 '19 at 18:43
  • @Dimensionless why is it adviced against? If I want my component to have certain custom behavior when clicked, I don't see any reason why my component would want to know about it. – Robo Robok Sep 18 '19 at 22:23
  • @RoboRobok Ther is nothing intrinsically wrong in using it, it just requires more attention. You can use the native modifier to bind the event to the root element of the component, but in doing so you are not specifying inside the component that there is an event on the root element of the component. Let's say you have input component and you are listening for focus event through native modifiers, and in future, you refactor the component and wrap the input element with a div. Now there will be no focus event and the event will never be fired. – Deiknymi Sep 20 '19 at 16:22
  • @Dimensionless makes perfect sense for `focus`, not sure about as generic event as `click` though. – Robo Robok Sep 20 '19 at 16:32
  • i also don't understand why `native` is required for `click`. eg. with a – Shomari Jan 25 '20 at 19:36
  • A warning in Vue.js dev mode would be nice, quite the gotcha. – AndrewJC Jun 05 '20 at 20:20
66

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">
Adam
  • 3,829
  • 1
  • 21
  • 31
Jim McNeely
  • 798
  • 5
  • 7
  • 12
    I believe this to be the correct answer. Handle within the component the linking of the events. Do not concern the parent component of what "version" of the click event to call. I actually implement it as the answer below in the component `@click="$emit('click')"` and that way the parent component just use the regular `@click` – Nelson Rodriguez Aug 28 '18 at 19:43
  • 2
    I'm a little confused. Is the $emit part maybe supposed to be in the test component template? – Stefan Fabian Nov 21 '18 at 14:59
  • 2
    What @NelsonRodriguez said. Use `@click="$emit('click')"`. – Nifel Dec 10 '19 at 08:51
  • This is definitely the more explicit solution when compared to the `.native` modifier, and makes the parent component less dependent on the child's implementation details, which is almost always a plus. – zcoop98 May 24 '21 at 18:01
32

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>
tony19
  • 125,647
  • 18
  • 229
  • 307
OddMorning
  • 451
  • 4
  • 5
29

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

Using $listeners:

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>

Using $emit method:

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

Using v-bind="$attrs":

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>
tony19
  • 125,647
  • 18
  • 229
  • 307
palaѕн
  • 72,112
  • 17
  • 116
  • 136
22

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>
JJJSchmidt
  • 820
  • 6
  • 13
Francis Nepomuceno
  • 4,935
  • 4
  • 29
  • 35
  • 11
    where does this go? Why do you put it there? Add a little more details for people viewing this answer, please? – mix3d Dec 03 '18 at 13:56
  • In this example this would be placed on the div tag in the Test.vue component. You can then use v-on:click="testFunction" or @click="testFunction" when use the component test in App.vue – Tim Wickstrom Jan 29 '19 at 21:35
  • I changed it to `$emit` but nothing is happening. Do I need to do something in addition to `$emit`? https://jsfiddle.net/xwvhy6a3/ – Richard Barraclough Mar 15 '20 at 11:09
  • @RichardBarraclough your component now emits your custom event "clickTreeItem". Next is to handle what to do with that event in the usage of that component: v-on:myEvent="myMethod" – Francis Nepomuceno Mar 16 '20 at 14:02
8

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')".

2

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.

Penny Liu
  • 15,447
  • 5
  • 79
  • 98
giapnh
  • 2,950
  • 24
  • 20
0

From the documentation:

Due to limitations in JavaScript, Vue cannot detect the following changes to an array:

  1. When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
  2. 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
}
tony19
  • 125,647
  • 18
  • 229
  • 307
Andris
  • 3,895
  • 2
  • 24
  • 27
0

App.vue

<div id="app">
    <test @itemClicked="testFunction($event)"/>
</div>

Test.vue

<div @click="$emit('itemClicked', data)">
     click here
</div>
Farzad.Kamali
  • 553
  • 4
  • 10