How Vue data flow works – A practical example

When I started using Vue I fell in love with it. Easier than Angular, the front-end framework I was using, and a lot of fun to use. But as I was learning, I was overwhelmed by Vue data flow options. How can I pass data to a sibling component? How can I pass down data to a children component? What about another component where they have no relationship?

Let’s analyze all the ways you can pass around data in Vue, and which one should you use and when.

If you need a less technical, more conceptual explanation, here’s one. Read it before keep reading if you are a total beginner in Vue


Table of contents
Our options
Our code
Sharing data between the same component
Props and $emit
EventBus
Vuex
Cool…Which one I should use then?
More information

Our options

We have 4 options to pass data in Vue:

  • In the same component
  • Props/$emit
  • EventBus
  • Vuex store

And all of 4 are useful. To fix something sometimes you need a hammer, sometimes duck tape. There is no “universal tool” you can buy and discard every other tool. You need all of them an learn their place and way to use. Let’s learn them.


Our code

To compare them, I created a small Vue project: A shopping cart.

Nothing fancy, just a website with two parts: One listing items to buy, their prices and the amount available, and the other one lists which items you have bought, how many and the total sum.

Yes. The design is simplistic. This is the best I can do.

As you can see, there is a menu with four links: once to each option I mentioned before.

In each case you can click the ‘+’ sign to add an item to the shopping cart, removing it from the store. And a ‘-‘ to do the opposite. And in each case, the ‘total’ of your cart would be re-calculated.

You can download it here: https://github.com/david1707/shopping-cart


Sharing data between the same component

The first and easier way is when you have everything in the same component. Both “items” (containing all items to buy) and “shoppingCart” (containing all items selected) are at the same Vue Component, making easy to interact between them.

<script>
export default {
  name: 'same-component',
  data() {
    return {
      items: [
        {name: 'Bycicle', price: 560, amount: 12},
        {name: 'TV set', price: 450, amount: 3},
        {name: 'Laptop', price: 1230, amount: 24},
        {name: 'Phone', price: 220, amount: 42},
        {name: 'Gaming chair', price: 170, amount: 33}
      ],
      shoppingCart: [],
    }
  },
  computed: {
    totalShopping() {
      return this.shoppingCart.length == 0 ? 0 :
        this.shoppingCart.map(el => el.price * el.amount)
                          .reduce((total, actual) => total + actual)     
    }
  },
  methods: {
    addItem(index) {
      const elementName = this.items[index].name
      const elementPrice = this.items[index].price

      this.items[index].amount--;

      // Check if the current item is already on the shoppingCart
      const elementIndex = this.shoppingCart.findIndex(el => el.name == elementName)

      if(elementIndex != -1) {
        this.shoppingCart[elementIndex].amount++;
      } else {
        this.shoppingCart.push({name: elementName, price: elementPrice, amount: 1})
      }
    },
    removeItem(itemIndex) {
      const elementName = this.items[itemIndex].name

      const elementIndex = this.shoppingCart.findIndex(el => el.name == elementName)
      if(elementIndex != -1) {
        this.shoppingCart[elementIndex].amount--;
        this.items[itemIndex].amount++;

        if(this.shoppingCart[elementIndex].amount === 0) {
          this.shoppingCart.splice(elementIndex, 1);
        }
      }
    }
  }
}
</script>

The code is pretty clear. Items and ShoppingCart are arrays containing the data, we have a computed variable, totalShopping that calculates how much you are going to spend, and an addItem and removeItem methods that do what their names say.

Even if you are a new Javascript programmer, can understand everything that happens there.

The code is within the same component and it’s easy to understand.


Props and $emit

Vue data flow - Props & Emit

But now things get complicated. What happens when we have a 4-level component web application? What when we have a container component with 3 siblings components?

In this example, we have a parent component, the container component, and two child component: An Item Side component and a Total Side component:

Until now, the methods and data were in the same component. Now they are separated and have to interact between them. When we click on the ‘+’ button, we have to remove one item from that type on the Item side component, but also add one at the Total side component. But also pass the price.

Sadly, props and $emit only use a parent/child relationship: You can’t pass information directly to another component. Props pass data to a children component, $emit sends data to its parent.

So, if we want to pass data from the Item component to the Total component, we need to use $emit to send the data up to the parent Container from the Item, then down to the Total component via props. Let’s see the relevant code:

ItemSideComponent.vue:

<script>
export default {
  name: 'props-item-side',
  data() {
    return {
      items: [
        {name: 'Bycicle', price: 560, amount: 12},
        {name: 'TV set', price: 450, amount: 3},
        {name: 'Laptop', price: 1230, amount: 24},
        {name: 'Phone', price: 220, amount: 42},
        {name: 'Gaming chair', price: 170, amount: 33}
      ],
    }
  },
  methods: {
    addItem(index) {
      const elementName = this.items[index].name
      const elementPrice = this.items[index].price

      this.items[index].amount--;

      this.$emit('addItem', {elementName, elementPrice})
    },

  },
}
</script>

Here, the addItem method takes the name and price, and “emits” a signal with the ‘addItem’ flag: Any component listening to this flag will notice.

Let’s see the parent component, Container, that should be listening to this:

<template>
  <div class="props-elements">
    <div class="container">
      <props-item-side @addItem="addItem" />
      <props-total-side :newItem="item" />
    </div>
  </div>
</template>

<script>
import PropsItemSide from '@/components/PropsItemSide'
import PropsTotalSide from '@/components/PropsTotalSide'

export default {
  name: 'props-elements',
  components: {
    PropsItemSide,
    PropsTotalSide
  },
  data() {
    return {
      item: '',
    }
  },
  methods: {
    addItem(data) {
      this.item = data;
    },
  }
}
</script>

As you can see, we are listening to the ‘addItem’ signal we emitted at the props-item-side element, and as soon we receive anything, we’ll call the method addItem that will change the item object. Now we have to pass it down to the props-total-side. For that, we need to use props.

<props-total-side :newItem=”item” /> sends down the item object as newItem, and it will be received as prop:

<script>
export default {
  name: 'props-total-side',
  data() {
    return {
      shoppingCart: [],
    }
  },
  props: ['newItem'],
  watch: {
    newItem() {
      const {elementName, elementPrice} = this.newItem
      const elementIndex = this.shoppingCart.findIndex(el => el.name == elementName)

      if(elementIndex != -1) {
        this.shoppingCart[elementIndex].amount++;
      } else {
        this.shoppingCart.push({name: elementName, price: elementPrice, amount: 1})
      }
    },
  }
}
</script>

And now we do the second part of the method: Adding it to the shoppingCart array.

As you can see, the method contains the same code, but it is split between 3 components. We have to do this as we are affecting different components.

Now you may think: What if we have to pass some data to a component inside a component inside a component inside a component inside…? Do I have to use props and/or emits on each one?

The answer is yes. Unless we use an EventBus.


EventBus

Vue data flow - EventBus

If our project is complex, with a lot of components interacting with data, you’ll go crazy. Imagine a small project with 12 components, passing up and down data. It should be a better way to do this, right?

Luckily, it is.

EventBus has the same principle of props and $emit, but as a super-parent.

Every single component is children of the Eventbus, so no matter how far away are two components sharing data: They’ll always have the EventBus as parent component.

So now, we just need to interact between the component sending the data and the one receiving the data. EventBus just acts as “middleman”.

Let’s see the code. First, we create a root level file called event-bus.js:

import Vue from 'vue';

export const EventBus = new Vue();

Yep, just like that. Now EventBus can be imported and we can use it. Here it is our ItemSideComponent:

<script>
import { EventBus } from '../event-bus.js';

export default {
  name: 'event-bus-item-side',
  data() {
    return {
      items: [
        {name: 'Bycicle', price: 560, amount: 12},
        {name: 'TV set', price: 450, amount: 3},
        {name: 'Laptop', price: 1230, amount: 24},
        {name: 'Phone', price: 220, amount: 42},
        {name: 'Gaming chair', price: 170, amount: 33}
      ],
    }
  },
  methods: {
    addItem(index) {
      const elementName = this.items[index].name
      const elementPrice = this.items[index].price

      this.items[index].amount--;

      EventBus.$emit('addItem', {elementName, elementPrice})
    },
  },
}

Just similar to using props and $emit, but without using any other component. Now, our TotalSideComponent:

<script>
import { EventBus } from '../event-bus.js';

export default {
  name: 'event-bus-total-side',
  data() {
    return {
      shoppingCart: [],
    }
  },
  methods: {
    addItem(item) {
      const {elementName, elementPrice} = item
      const elementIndex = this.shoppingCart.findIndex(el => el.name == elementName)

      if(elementIndex != -1) {
        this.shoppingCart[elementIndex].amount++;
      } else {
        this.shoppingCart.push({name: elementName, price: elementPrice, amount: 1})
      }
    },
  },
  created() {
    EventBus.$on('addItem', this.addItem)
  },
  destroyed() {
    EventBus.$off('addItem', this.addItem)
  }
}
</script>

Now, as you see, we import the EventBus and we start listening to the ‘addItem’ flag, as shown on ‘created’. As soon we get a signal, we trigger the addItem method. Way simpler.


Vuex

Vue data flow - Vuex

EventBus was an improvement, but we still have to send signals to an EventBus, listening to them and, if our project is big enough, will be a bit hard to chase where is everything. And we can even have duplicated code if we use methods in two or more components.

Vuex fixes that.

Vuex is a state management library that serves as a centralized store, being the single source of truth of your project.

Ok. Let me explain.

Remember how we passed up and down the data, have a few methods, etc? Now imagine the same with a project of 6 components. Or 20. It would be madness, right?

Imagine that you create a store where you can add items, then pass said items to the buying page, then the confirm, then…

Vuex acts as a single source of truth with its own state. Every component pulls the data from Vuex state. If we need some data, we pull it from Vuex state. Not from other components but just the state. As every component information is fetched from Vuex state, there is no conflict.

Let’s see the Vuex store:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    items: [
      {name: 'Bycicle', price: 560, amount: 12},
      {name: 'TV set', price: 450, amount: 3},
      {name: 'Laptop', price: 1230, amount: 24},
      {name: 'Phone', price: 220, amount: 42},
      {name: 'Gaming chair', price: 170, amount: 33}
    ],
    shoppingCart: []
  },
  getters: {
    getItems: state => {
      return state.items
    },
    getShoppingCart: state => {
      return state.shoppingCart
    },
    getTotalShopping: state => {
        return state.shoppingCart.length == 0 ? 0 :
          state.shoppingCart.map(el => el.price * el.amount)
                            .reduce((total, actual) => total + actual)   
    },
  },
  mutations: {
    addItem(state, index) {
      const elementName = state.items[index].name
      const elementPrice = state.items[index].price

      state.items[index].amount--;

      const elementIndex = state.shoppingCart.findIndex(el => el.name == elementName)

      if(elementIndex != -1) {
        state.shoppingCart[elementIndex].amount++;
      } else {
        state.shoppingCart.push({name: elementName, price: elementPrice, amount: 1})
      }
    },
    removeItem(state, index) {
      const elementName = state.items[index].name
      const elementIndex = state.shoppingCart.findIndex(el => el.name == elementName )

      if(elementIndex != -1) {
        state.shoppingCart[elementIndex].amount--;

        if(state.shoppingCart[elementIndex].amount === 0) {
          state.shoppingCart.splice(elementIndex, 1);
        }
      }

      state.items.map(el => {
        if(el.name == elementName) {
          el.amount++;
        }
      })
    }
  },
})

Cool, now we have all the logic in one single file! This way when we have to change anything, we just need to go to the store.js file to modify it. No more chasing code in components!

Also, this ensures we follow the “Don’t Repeat Yourself” directive.

Now, every component just need access to each method and state values.

ItemSideComponent:

<script>
import { mapGetters, mapMutations } from 'vuex'

export default {
  name: 'vuex-item-side',
  computed: {
    ...mapGetters(
      {items: 'getItems'}
    )
  },
  methods: {
    ...mapMutations(['addItem', 'removeItem']),
  },
}
</script>

TotalSideComponent:

<script>
import { mapGetters, mapMutations } from 'vuex'

export default {
  name: 'vuex-total-side',
  computed: {
    ...mapGetters({
      shoppingCart: 'getShoppingCart',
      totalShopping: 'getTotalShopping'
    }),
  },
}
</script>

Way less complex. And if you need to create more components that use some functionality, you just need to import the method(s) and that’s it.

Of course, this adds a bit of complexity as you have to set Vuex, the store, and on big applications you need to split your store.js into modules (auth.module.js, user.module.js, etc), but it is worth as will save you a lot of pain.


Cool…Which one I should use then?

All of them. Seriously.

Every way has its own place, you just need to use your own judgement. But let me help you:

  • Simple component: Used only to manipulate the data already existing in the component.
  • Props and $emit. Used to pass down/up data between parent/children components. You can manage to pass data between components on different levels, but it is unnecessarily hard. Only use it when you are using shared components as custom buttons and the like to pass down as props data (For example, when you import components).
  • EventBus. Used to pass data between components. Distance doesn’t matter here as you are using an EventBus component that acts as middle-man of each component. Use it to pass data between components with two or more degrees of separation or to trigger functions.
  • Vuex. My default choice unless what I’m doing is super simple. A single source of truth is great and makes impossible to lose data. Also, the re-usability of methods is great.

I cannot stress this: Every option has its uses, learn all of them and apply the one that suits best to your project.


More information:

Props

$emit

EventBus

Vuex

I have learnt Vue doing the 100 Days of code Challenge. And this is what I learnt after doing it: Learning Vue: My Five Ws


My Youtube tutorial videos

Reach to me on Twitter

Read more posts