Component communication in Vue3: sibling, parent, and child components
A while back, I made a video on building a slide-over dialog component using HeadlessUI and VueJS. Inside that component, a button would open the slide-over.
However, there was no way to open the slide-over from the outside - making it tricky to place the button in different places - like in a top navigation bar.
It all boils down to: how can vue components communicate with each other?
It depends. But first, here’s a stripped-down version of the slide-over component:
1<!-- ShoppingCart.vue --> 2<script setup> 3import {ref} from 'vue'; 4import {Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot} from '@headlessui/vue'; 5 6let isOpened = ref(false); 7 8function setIsOpened(value) { 9 isOpened.value = value;10}11</script>12 13<template>14 <button @click="setIsOpened(true)">15 Open cart16 </button>17 18 <TransitionRoot :show="isOpened" appear as="template">19 <Dialog :open="isOpened" @close="setIsOpened(false)">20 <TransitionChild as="template">21 <DialogOverlay/>22 </TransitionChild>23 24 <TransitionChild>25 <DialogTitle>Cart summary</DialogTitle>26 <button @click="setIsOpened(false)">27 Close28 </button>29 </TransitionChild>30 </Dialog>31 </TransitionRoot>32</template>
Parent-Child component communication
Parent components communicate with their child components through props. Child components communicate back by emitting events.
If we want to open the slide-over from the outside, we need to move the controlling state into the parent component, pass it as a prop to the child component, and then set a listener for an event the child will emit to toggle the isOpened
value.
1<!-- ParentComponent.vue --> 2<script setup> 3import ShoppingCart from "./ShoppingCart.vue"; 4import {ref} from "vue"; 5 6const isOpened = ref(false); 7</script> 8 9<template>10 <button @click="isOpened = true">Open cart</button>11 <ShoppingCart :is-opened="isOpened" @toggle="(value) => isOpened = value"/>12</template>
The child component would have to accept the prop, use it to decide wether or not the slide-over should open, and emit a toggle
event to update isOpened
.
1<!-- ShoppingCart.vue --> 2<script setup> 3import {ref} from 'vue'; 4import {Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot} from '@headlessui/vue'; 5 6const props = defineProps({ 7 isOpened: Boolean 8}); 9 10const emit = defineEmits(['toggle']);11 12function setIsOpened(value) {13 emit('toggle', value);14}15</script>16 17<template>18 <button @click="setIsOpened(true)">19 Open cart20 </button>21 22 <TransitionRoot :show="props.isOpened" appear as="template">23 <Dialog :open="props.isOpened" @close="setIsOpened(false)">24 <TransitionChild as="template">25 <DialogOverlay/>26 </TransitionChild>27 28 <TransitionChild>29 <DialogTitle>Cart summary</DialogTitle>30 <button @click="setIsOpened(false)">31 Close32 </button>33 </TransitionChild>34 </Dialog>35 </TransitionRoot>36</template>
Sibling components communication
If we were to move the “Open cart” button from the parent component into a different component, say a TopNavigation
component, the button would stop working.
That’s because the relationship between those components changed. They are no longer parent-child components; they are sibling components, and we need a different pattern for that.
If before we moved the controlling state from the child to its parent, we now need to move it into a central piece of state to be shared between multiple components - this is also known as a store, and Vue3 makes it easy to do this.
We’ll create a new file, stores/cart.js
that will simply export a reactive value:
1// stores/cart.js2export const cart = ref({3 isOpen: false,4 setIsOpen(value) {5 this.isOpen = value;6 }7});
All we have to do is import the cart store and replace the old isOpened
references with cart.isOpened
and replace setIsOpened()
with cart.setIsOpened()
:
1<!-- TopNavigation.vue --> 2<script setup> 3import {cart} from "../stores/cart.js" 4</script> 5 6<template> 7 <nav> 8 <button @click="cart.setIsOpened(true)">Open cart</button> 9 </nav>10</template>
1<!-- ShoppingCart.vue --> 2<script setup> 3import {Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot} from '@headlessui/vue'; 4import {cart} from "../stores/cart.js"; 5</script> 6 7<template> 8 <button @click="cart.setIsOpened(true)"> 9 Open cart10 </button>11 12 <TransitionRoot :show="cart.isOpened" appear as="template">13 <Dialog :open="cart.isOpened" @close="cart.setIsOpened(false)">14 <TransitionChild as="template">15 <DialogOverlay/>16 </TransitionChild>17 18 <TransitionChild>19 <DialogTitle>Cart summary</DialogTitle>20 <button @click="cart.setIsOpened(false)">21 Close22 </button>23 </TransitionChild>24 </Dialog>25 </TransitionRoot>26</template>
Closing & caution
If you need to communicate between parent and child components, pass data through props and emit events from the child component.
If you need to communicate between sibling components, create a central piece of state that can be shared between the components that need it.
However, if you’re working on a large application, consider using Pinia - which is the state management library recommended by the Vue core team: https://pinia.vuejs.org/
-
1Managing SVG icons04:57
-
2Programmatically focus elements to provide a better user experience08:29
-
3Building a CopyToClipboard renderless component in VueJS08:29
-
4Create reusable form components using Vuejs 3 multiple v-models06:58
-
5Vue 3: ref and reactive05:34
-
6Loading scripts inside Vue components08:14
-
7Google maps autocomplete with VueJS10:52
-
8From options API to script setup using the composition API07:04
-
9VueJS loading button with TailwindCSS06:48
-
Component communication in Vue3: sibling, parent, and child components06:50
-
11Building a Vue JS textarea autoresize component05:13
-
12Vue watch multiple values at once03:55
-
13Building a Vue tooltip component using TailwindCSS and Floating UI16:27
-
14Vue templates: avoid functions, use computed properties instead05:12
-
15Build an Alert component using Vue, TailwindCSS and CVA17:23
-
16When to pass function props in vue 302:35
-
17Reusable avatar component11:23
-
18Table of Contents component using VueJS13:43
-
19Cleaner form fields in VueJS14:54
-
20Button component12:16
-
21Vue async combobox component19:14
-
22Vue State and LocalStorage: Perfect Sync Made Simple!07:44
-
23Nuxt 3 environment variables config08:36
-
24Streamline Vue Forms with this useForm composable07:36
-
1Managing SVG icons04:57
-
2Programmatically focus elements to provide a better user experience08:29
-
3Building a CopyToClipboard renderless component in VueJS08:29
-
4Create reusable form components using Vuejs 3 multiple v-models06:58
-
5Vue 3: ref and reactive05:34
-
6Loading scripts inside Vue components08:14
-
7Google maps autocomplete with VueJS10:52
-
8From options API to script setup using the composition API07:04
-
9VueJS loading button with TailwindCSS06:48
-
Component communication in Vue3: sibling, parent, and child components06:50
-
11Building a Vue JS textarea autoresize component05:13
-
12Vue watch multiple values at once03:55
-
13Building a Vue tooltip component using TailwindCSS and Floating UI16:27
-
14Vue templates: avoid functions, use computed properties instead05:12
-
15Build an Alert component using Vue, TailwindCSS and CVA17:23
-
16When to pass function props in vue 302:35
-
17Reusable avatar component11:23
-
18Table of Contents component using VueJS13:43
-
19Cleaner form fields in VueJS14:54
-
20Button component12:16
-
21Vue async combobox component19:14
-
22Vue State and LocalStorage: Perfect Sync Made Simple!07:44
-
23Nuxt 3 environment variables config08:36
-
24Streamline Vue Forms with this useForm composable07:36