Communication between components in VueJS

All the ways I could think of on how to make your components communicate with each other.

Miloš Milošević
Webtips
12 min readAug 25, 2020

--

Despite the usual intros I’ve seen in these kinds of articles, I’m not going to go into describing Vue here. If you are here, it means you know what VueJS is, or at least you’ve heard of it, and you want to learn more about it. So, let’s get right into it.

Note: With Vue 3 around the corner, there are some changes coming, but the core concepts of components and communication between them will not change and all these principles will still apply when the v3 hits the metaphorical shelves.

Note 2: This might be a bit longer article than you’re used to.

A word about components

If you are familiar with components in VueJS, you can probably skip this part, because it’s essentially an intro for beginners.

You can think of the components as the building blocks of your application. If you’ve worked on any project before, you probably noticed that web pages and apps can be broken down into smaller chunks that can often be reused with small alterations or no changes at all. That’s exactly the point of components — building the app using smaller parts and combining them together. As VueJS documentation says, the components are an abstraction that lets us build large-scale applications composed of small, self-contained, and often reusable components.

For better understanding, take a look at the diagram below, it’s a similar diagram to the one on the official VueJS documentation website:

Image by author

As you can see, components can be a part of other components, and also can contain other components. We can refer to the components as parent and child components, depending on where they are located. On the illustration above, component A is a parent component to the components B, C, and D, and component C is a parent of the components F and G. Components B, C, D are child components of component A, and F and G are child components of component C.

When building the apps with VueJS, every .vue file that you create is a component and it’s essentially a Vue instance with some predefined options. Each of these components has its own methods, data, props, computed etc, and when added in your app will have its scope.

Although components are sometimes self-contained and do not have any information going in or out, there are times when you need them to communicate with other parts of your application, for example with parent component, their child components, or with other components that are located somewhere far away it the component tree. They could be sending some data, or receiving it, or reacting to the changes that you made in some other components.

For more information about components and component registration, take a look at the official VueJS documentation.

Based on their place in app structure, let’s see what are the ways to communicate between the components.

Parent to child communication

This is one of the most common scenarios — you have a parent component from which you want to pass some data to the child component. For example, you have an API call in the parent component and want to send the results to a child, or you have multiple instances of the same component as children and want to pass them different data.

There are several ways that you can pass data to child components or manipulate existing data in child instances, and we’ll cover them all, but the best way to do this would be using props.

Props

Props are custom attributes that you can use to pass any data to a child component — strings, objects, arrays, numbers, even functions. Once passed, they become property on that component instance and you can use them inside it. You can have as many props as you want, but keep in mind that a component with a lot of props might become hard to read and maintain. If you need to pass a lot of different data, combine them into an object, and then pass them as a single prop.

Remember that passing data with props has a one-way data flow, which means that data “flows” from the parent to child and not the other way around, so if you update the bound prop in the parent component it will be updated in the child component. You really shouldn’t be trying to mutate the prop you passed directly in the child component, as this is a huge anti-pattern, and fortunately, Vue will give you a console warning if you attempt something like this.

We’ll get to updating props from children in a little bit, but first, let’s take a look at the following example.

Screenshot by author

The text with click count is a separate component, and the button is in a parent component.

We’re going with the simplest example — we have a component that contains the sentence and the button is in the parent component, that will update the counter when we click it.

There is not much going on here, but the code for our click-counter component could look something like this:

And then our parent component, where we have our button and method for updating click count, could look like this:

As you can see, we assigned the clickCount variable to a prop counter and are updating it when we click the button. Thanks to reactivity in Vue, this value will be automatically updated after each click and as the change is detected.

This is a fairly simple and straightforward example, but you get the gist. You should use this pattern whenever possible, as it is the cleanest way to pass data to children's components. Also, I would advise that whenever you’re using the prop values in your children components you write them as $props.propName. Props, data variables, and computed values cannot have the same name, so you cannot duplicate them, but it is a good practice to write this way because you will know where your values are coming from just by looking at the code.

Direct access to children properties

Direct access is another way to access and update values in children components and you could sometimes encounter the need for this pattern, but it’s best to avoid using it altogether, as it can become confusing what is changing your values and where you’re doing it. So, if you can do it with props, go for props.

But let’s see how this would work on our example above. I will have to change the components a little bit, so our counter is not using the prop anymore, but scoped variable from the child component.

As you can see in method increaseCounter in Home.vue component, I’m accessing directly the variable inside the child component, using ref, and updating it with a new value.

You could access counter value using $children instance property.

But don’t do this — it can look straightforward in this simple example, but once you have multiple child components their order in $children cannot be guaranteed, and you could have a hard time tracking your changes.

Child to parent communication

Continuing from our example above, let’s say we want to display a message in our parent component once our counter reaches 5 clicks. If we are using props to pass a value to our child component, the parent component is also aware of the value of the counter, so you would place logic that displays a message when the counter reaches 5 inside the parent component too. But let’s do something different in this example — let’s create a watcher in our child component that will tell a parent component when to show a message.

The best way to do this would be by using events.

Events

As I said earlier, normally, a logic should be a little bit different for the following example, but we will flip it just for this example. As you can see below, I added a watcher in <click-counter> component, that emits an event limit-reached to a parent component telling it that a message should be displayed.

Note that similar to props that flow in one way, downward from parent to child, you can think of events also flowing in one way, but upward, from child to parent.

Back to our example, in our parent component, I added a new line that is hidden until the showMessage property is set to true along with a method that will be triggered once the event is fired.

As soon as our prop counter in the child component reaches 6, the watcher will catch that and emit the event to our parent component, triggering method showMessage that changes property messageShown from false to true and displaying our message below the button.

Events can also carry any data that we want to pass to a parent component, you simply include it as a second parameter after the name of the event, but that is a whole other area and I will leave that for some future text.

Direct access to parent properties

Similar to what we did when we directly accessed a variable inside the child component, we can do something like in the reverse situation. This is, again, considered bad practice, as you are breaking the separation of concerns pattern and changing the values outside of components where they are located, so try to avoid it.

For this example, instead of emitting a custom event, we will directly access showMessage variable in the parent component and set it to true.

You may encounter a situation where it may seem easier to do things this way instead of emitting an event, but in that case, you will have to decide if you should go with the easier solution and possibly encounter a problem later, unable to find what’s changing your values, or go with a solution that involves slightly more typing but that would be easier to maintain later. I would always opt for the latter.

Omni-communication

We’ve seen how you can communicate between parent and child components, but what do you do when you want to pass data between different components on the same level? Or components on different levels, but not related to each other?

Image by author

Sure, you could emit data from one child to parent, and then catch that event and pass a prop to another child, getting into a prop-drilling trap, but that’s not really efficient.

In such cases, we can also use events or Vuex.

$root emit

The thing with custom events emitted from child is that they will travel only up to parent level, or more accurately — they will be emitted on child instance and then you can listen on parent level for those events. Now, in cases such as our little example, $root emit doesn’t make much sense, but let’s say you want to catch an event emitted from grand-grand-grand-child, or some such deeply nested component, it would be tedious to emit custom event to parent, and then make that parent to emit an event to its parent and so on. In these cases, it would be better to use $root.$emit or your own custom event bus (we’ll cover that later).

Just a small digression here, remember how we used $child and $parent in our previous examples? Those represent component instances relative to where they were used. $root is also a component instance, but it’s the root instance (usually App.vue) and it will be the same no matter where you used it.

To emit a root event, we would do something like this

That event will be then emitted on the root instance and to catch it, we need to set a listener to the root instance also. This is usually done in created hook, and the code can look something like this:

As the root instance is the same in all other components, you can set this listener wherever you need it — it will work in both parent and child components.

Now, I don’t think this is a bad practice per se, but you should be careful when using it, it can be slow in a big project and there are better solutions in these situations, like Vuex or custom event bus.

Also, don’t forget to remove your listeners once you don’t need them anymore. Following the example above, to detach a root listener you can add the following code in your beforeDestroy hook:

Custom event bus

When you have multiple components that need to communicate with each other, a custom event bus is a nice and easy way to achieve this.

Let’s see how that would work on our previous example. First, you need to create an ES6 module that imports Vue and exports it as a constant.

Then, you need to import that into both your child component that will emit custom event and the component that should receive it. I’m using child and parent components here, but this will work between any two components.

What we did here is we created a separate Vue instance which we are using to attach our custom events. It’s sort of like a separate, empty lane where you can send and catch events.

You can see it’s similar to $root event, but here we are not attaching events to our main Vue instance, so performance should be better, especially in large projects.

The main danger here is if you duplicate your event names, so it’s a good idea to come up with a strategy to avoid that, for example, make a convention for naming your events so you know what they do and where they come from. In our example, we could name our event as ‘counter:limit-reached’. That way we know what it does and it’s less likely we’ll duplicate our event name.

Also, although there is no limit on how many new Vue instances and event buses you can create, it’s probably best to keep it at just one.

Again, don't forget to detach your events with CustomEventBus.$off('custom-event').

Vuex

You have probably heard by now about Vuex. It’s a well-known state management pattern and library for VueJS that is design to tackle the more complex projects where you have multiple components sharing and using the same data.

You can think of it as a local storage for your application, only it’s more powerful. With it, you can store, modify and read data from one component, and react to those changes in another component that could be far away in a component tree.

Vuex is another kind of monster and there’s simply no room here to write about everything it can do, but if you are writing anything more complex than a to-do list app, it’s likely you’re gonna need it.

When working with it, I’d like to suggest that you always use Vuex modules, it’s a great way to keep your code organized and easier to maintain.

So, how can we use it in the example we had so far? I’m going to show you how to do it with modules, although it’s a bit of overkill for this example, you should get an idea of its advantages.

First, this is how the code structure for Vuex with modules looks in most of my projects. You can see I have a folder ‘store’ and in it a folder named ‘modules’ where I keep my modules in charge for different parts of the application. Lastly, there’s an ‘index.js’ file that imports all this and connects everything.

This is how our module for counter can look like — it has a state for both counter and message, getters that we’ll use to show our state, and actions that will commit the changes:

And imported in index.js for store

Let’s see how our components will look like when adapted to use Vuex

As you can see, I got rid of all the login in components, it’s all controlled by Vuex now. I can call action to increase clicks or show the current number of it without having to pass any props or events and from any component I want (as long as I include mapActions and mapGetters Vuex helpers).

Finally, the end!

I hope that I was able to explain in an understandable way different concepts of communication between components and that you will be able to use this in your apps. Of course, it all boils down to the needs of your application, you will probably not use all of these in your app, but whatever you use, keep it consistent.

While writing this I remembered a few more crazy ways to pass data, like accessing the DOM element directly and changing value, or sending it to data attributes, but that’s simply absurd to do with modern JS frameworks.

Remember to keep things simple, but always keep in mind code maintainability — you or your colleagues must be able to recognize what the code does and how it’s doing it.

This article was edited to include the part about detaching events.

--

--

Miloš Milošević
Webtips

Front end developer, focused on VueJS, at the moment. Writing about Javascript, VueJS and other front end technologies. Find me on twitter @chameleon_mind