Child to Parent Communication in Vue 3

Jacob Tschirhart
4 min readJun 6, 2021

There are two primary ways to get child to parent communication in Vue 3. Using props and using a Vuex store. In this tutorial, we’ll explore both of those below.

Child to Parent Communication with Props

The first way child components can communicate to their parent components without pulling in extra node packages is by utilizing the props feature of the Vue framework. You may have used props before for passing data from the parent to the child but with a few small changes we can pass data changes back to the parent.

What you may already know about props for passing data from the parent to the child probably looks something like this

<child :counter="counter" />

Now if we want the counter value to be reactive to what happens in the child, we can do that by simply adding v-model to the prop as follows

<child v-model:counter="counter" />

Let’s say the child might need to do something to the counter value. Your code might look something like this

<template>
<div>Child Counter: {{ counter }} </div>
<button @click="increment">Increment Counter</button>
</template>
<script>
export default {
props: {
counter: {
type: Number
}
},
data: () => ({
childCounter: 0
}),
created() {
// the prop needs to be re-assigned here. Vue doesn't like prop mutation
this.childCounter = this.counter
},
methods: {
increment() {
this.childCounter++
}
}
}
</script>

But that doesn’t update the value of the counter the parent has knowledge of. So what do we do?

We’ve already told the child component in the parent to listen to updates to counter with v-model . To hook into this listener, we’ll modify our increment method with this.$emit('update:myProp', newPropValue)

increment() {
this.childCounter++
this.$emit('update:counter', this.childCounter)
}

The full code looks like this:

Parent Component

#Parent.vue
<template>
<div>Parent Counter: {{ counter }}</div>
<child v-model:counter="counter" />
</template>
<script>
import Child from './Child'
export default {
components: {
Child,
},
data: () => ({
counter: 0,
}),
}
</script>

Child Component

<template>
<div>Child Counter: {{ childCounter }} </div>
<button @click="increment">Increment Counter</button>
</template>
<script>
export default {
props: {
counter: {
type: Number
}
},
data: () => ({
childCounter: 0
}),
created() {
// the prop needs to be re-assigned here. Vue doesn't like prop mutation
this.childCounter = this.counter
},
methods: {
increment() {
this.childCounter++
this.$emit('update:counter', this.childCounter)
}
}
}
</script>

Child to Parent Communication with a Vuex Store

Using props on components is just one way for child to relay data changes back to parents. Vue offers us another way to achieve this goal with the Vuex Store. A store is a place for data to live for the length of a session in our application. To get started with Vuex add, you’ll need to add it to your application. We need to add vuex@next to our project rather than vuex because vuex@next works with Vue 3 while vuex still works with Vue 2 at the time this article is written

$>npm i vuex@next

Then we’ll need to set up on our store in the application. First lets add the store script file. We’ll add to to this later

#src/store/store.js
import { createStore } from 'vuex'
export default createStore({})

And then register the store with your application

#src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store/store'
const app = createApp(App)
app.use(store)
app.mount('#app')

Once that’s done, we’ll need a state with a value that holds a counter as well a way to mutate that state

import { createStore } from 'vuex'const state = {
counter: 0
}
const mutations = {
increment: (state) => state.counter++
}
export default createStore({
state,
mutations
})

To retrieve that value from the store, all it takes is adding a computed property to your component that returns the value of the counter in the store. This could be anywhere in the application that you need the value, whether that’s the parent of a child doing the mutation or in a totally different place.

computed: {
counter() {
return store.state.counter
}
}

And to increment the counter, we just need to commit a mutation in a method on your component

methods: {
increment() {
store.commit('increment')
}
}

The resulting code will look like this:

Child Component

<template>
<div>Child Counter: {{ counter }} </div>
<button @click="increment">Increment Counter</button>
</template>
<script>
import store from '../store/store'
export default {
computed: {
counter() {
return store.state.counter
}
},
methods: {
increment() {
store.commit('increment')
}
}
}
</script>

Parent Component

<template>
<div>Parent Counter: {{ counter }}</div>
<child />
</template>
<script>
import store from '../store/store'
import Child from './Child'
export default {
components: {
Child,
},
computed: {
counter() {
return store.state.counter
}
}
}
</script>

Conclusion

We’ve explored the two primary ways components communicate in Vue 3. There are of course other solutions out there and different implementations of the store. It’s up to you to find the one that works best for you and the task you’re trying to accomplish.

My source code including the above examples can be found here: https://github.com/jacobtshirt/vue-child-parent-commucation

Vue Docs: https://v3.vuejs.org/

Vuex Docs: https://next.vuex.vuejs.org/

--

--