A Guide on How to use Emit in Vue

Johnny Simpson - May 9 '22 - - Dev Community

In Vue, data is typically passed from parent components, to their children in a uni-directional fashion. This is transferred with props, which are the properties or attributes we give to components.

For example, if we call a component PageOne, which has a prop called name, that name property would become available within the PageOne component itself, letting us do what we want to do with it. In that way, the data is passed down to the child component, when we declare it in a parent component or page:
How Components work in Vue

In most scenarios, props allow us to do everything we need to do with data. Sometimes, however, we need to emit data upwards - from a child component to its parent. For this, we use $emit, which lets us send data upwards, and then trigger an event in the parent component should an $emit event be fired.

How $emit works in Vue

There are three ways to fire $emit in Vue, depending on if you're using the Options API, Composition API, or inlining your $emit events. If you are unsure, you can read about the difference between the Composition API and Options API here.

  • this.$emit within the Options API.
  • $emit if used in your HTML template.
  • defineEmits and emit if used in the Composition API.

Let's take a look at how this works, through a silly example. Let's say we have a counter component, which looks like this:

<template>
    <button @click="$emit('counterEvent')">Click Me</button>
</template>
Enter fullscreen mode Exit fullscreen mode

This component is stored in a file called Counter.vue. Our component can't be changed as it's used in other places, but it does have an $emit event fired any time it is clicked. This is perfect, since we can use this in our parent component.

So what if we want to add this component somewhere - for example, in our App.vue file - and use it to display the value of our counter. Let's try doing that now:

<template>
    <h1>{{ counter }}</h1>
    <Counter @counter-event="incrCounter"/>
</template>

<script>
import Counter from './Counter.vue'
export default {
    // Add our components
    components: {
      Counter
    },
    // Store our data
    data() {
        return {
            counter: 0
        }
    },
    methods: {
        incrCounter: function() {
            this.counter += 1;
        }
    }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Let's break this down - first of all, we include our Counter. Since it has an $emit event called counterEvent, we can attach that to our Counter HTML. Whenever $emit fires, it'll fire the counterEvent, and thus the function within that property. Here, we run incrCounter any time counterEvent fires.

By doing that, we also increase our counter data by 1, since that is what incrCounter does. As such, we've emitted the click event upwards to our parent component.

Kebab Case

You may notice that when we defined our $emit event, we used camel case (counterEvent), but when tracking the event, we used kebab case (counter-event).

In Vue 3 it is fine to use counterEvent and counter-event interchangably since Vue 3 automatically converts counterEvent to counter-event. In Vue 2, this functionality does not exist, so just stick with counter-event for both.

Passing Data with $emit

Let's say instead, we want our component to define how much the counterEvent should increase by. If we want to do that, we can pass a second argument to the $emit function, which is the value:

<template>
    <button @click="$emit('counterEvent', 2)">Click Me</button>
</template>
Enter fullscreen mode Exit fullscreen mode

Here, we are passing the value 2 to our counterEvent. Let's go back to our App.vue file. To leverage this value in counterEvent, we need to write it as a function. Below, n is the value:

<template>
    <h1>{{ counter }}</h1>
    <Counter @counter-event="(n) => incrCounter(n)"/>
</template>

<script>
import Counter from './Counter.vue'
export default {
    // Add our components
    components: {
      Counter
    },
    // Store our data
    data() {
        return {
            counter: 0
        }
    },
    methods: {
        incrCounter: function(value) {
            this.counter += value;
        }
    }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Now, our counter will increase by the value put in the child component, allowing us to pass data to our parent component as well. As you'd expect, this is not limited to just numbers, but can include any data structure - including objects and strings.

Using $emit with the Options API

We've shown a quite simple example, but we could also have written our Counter.vue child component using a function instead. Here is an example with the Options API, using this.$emit:

<template>
    <button @click="emitFunction">Click Me</button>
</template>

<script>
export default {
  emits: [ 'counterEvent' ],
    methods: {
        emitFunction: function() {
            this.$emit('counterEvent', 2)
        }
    }
}
</script>
Enter fullscreen mode Exit fullscreen mode

This may prove to be a slightly cleaner way to use $emit, especially if you want to do other things along with using $emit whenever a button is clicked.

Adding your emit events to your prototype

You may note that we also defined our emit event in emits on the prototype. This is good practice for two reasons:

  • It lets you self-document the code by showing which emit events are possible in this component.
  • It helps you keep track of deprecated emits, since Vue will throw an error if an emit event is used but not found in the emits array.

Using $emit with the Composition API

We can use $emit with the Composition API - the only difference is we have to use defineEmits instead.

<template>
    <button @click="emitFunction">Click Me</button>
</template>

<script setup>
import { defineEmits } from 'vue'

const emit = defineEmits(['counterEvent']);
const emitFunction = function() {
    emit('counterEvent', 2)
}
</script>
Enter fullscreen mode Exit fullscreen mode

defineEmits is used to define a full list of all permissible emit events. Here, we only have one, counterEvent. If you had more than one, you could define them as so:

const emit = defineEmits(['counterEvent', 'anotherEvent', 'finalEvent']);
Enter fullscreen mode Exit fullscreen mode

If you use an emit event not listed in defineEmits, Vue will throw a warning, similar to using emits on the Options API. Otherwise, you can then use the emit() function to emit as usual, without needing to use the Options API at all.

Final Thoughts and Best Practices

Emit is a powerful tool for sending data back up to the parent when we have to. It means datastreams can be two way in Vue. When defining emit code, the two main best practices are:

  • Always define your emit events in either emits or defineEmits, which will help you keep your code clean and well documented
  • Normal convention in Vue 3 is to use kebab case (this-is-kebab-case) for HTML, and camel case (thisIsCamelCase) in script. As such, it is best to follow this convention here too.

I hope you've enjoyed this guide on how $emit works. Stay tuned for more Vue Content.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .