
Custom Events and Component Communication in Vue – `emit`, `provide/inject`, and When to Use a Store
} -->
Vue gives you a few powerful ways to handle communication between components. But which one to choose — and when?
Let’s break it down with practical examples and rules of thumb that I follow in my own projects.
emit
— Direct Parent-Child CommunicationThe most common and preferred way to pass events from a child to its parent is using emit
.
<!-- ChildComponent.vue -->
<template>
<button @click="$emit('increment')">+1</button>
</template>
<script setup lang="ts">
defineEmits(['increment'])
</script>
<!-- Parent.vue -->
<template>
<ChildComponent @increment="count++" />
</template>
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue'
let count = $ref(0)
</script>
Use emit when:
The component is used directly by the parent
The event is simple (like “clicked”, “submitted”, “closed”)
This allows deeply nested components to access shared values without having to pass props through every intermediate layer.
// Parent.vue
provide('theme', 'dark')
// Any deeply nested component
const theme = inject('theme')
Use provide/inject when:
vYou’re sharing context (like theme, user, or layout data)
Downsides:
vHarder to track dependencies than props or store
Sometimes components are far apart, or many parts of the app rely on the same data. That’s when a store is best.
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
},
},
})
<!-- Any component -->
<script setup lang="ts">
const counter = useCounterStore()
</script>
<template>
<button @click="counter.increment">Count is {{ counter.count }}</button>
</template>
Use a store when:
Data needs to be shared across multiple parts of the app
State is central and reactive
You want devtools integration, persistence, or modules
I use:
emit
for parent–child interaction
provide/inject for context-style values
Pinia when state is used in many places or gets too complex
Don’t overengineer communication. Start with props and emit. When things get messy, reach for provide/inject or a store.
Clear data flow = better code and easier debugging.
Bartłomiej Nowak
Programmer
Programmer focused on performance, simplicity, and good architecture. I enjoy working with modern JavaScript, TypeScript, and backend logic — building tools that scale and make sense.