Vue.js communication Part 3: all components
5 min read

Vue.js communication Part 3: all components

In the last two articles, we’ve discovered how communication works within the same component and a parent and child setup. We’ll talk about how to communicate from any component to any other component in this one. And for this, we’ll introduce Vuex.

What is Vuex, exactly?

Vuex is a centralized data store for all your components. Its state can only be changed by special functions that we’ll cover later.

This quote is how Vuex describes itself:

Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.

It is the official intro to Vuex from their website. There is a state, and Vuex will manage the state with mutations.

Vuex also has actions that usually commit mutations based on your business logic. At least that’s how I see it.

Let’s break it down

I’m not aiming to replace the official docs here. I’ll keep this nice and crispy, and I’ll add my take to the mix. I’ll link to the docs at the bottom of this article.

Example

const state = {
  isLoading: false,
  currentUser: null,
}

const mutations = {
  setIsLoading (state, isLoading) {
    state.isLoading = isLoading
  },
  setCurrentUser (state, user) {
    state.currentUser = user
  },
}

const actions = {
  async fetchCurrentUser ({ commit, state }, user) {
    // Don't do anything, it's already loading
    if (state.isLoading) {
      return
    }

    commit('isLoading', true)
    // Pseudocode
    const { user } = await graphql.request('fetchCurrentUser')
    commit('setCurrentUser', user)
    commit('isLoading', false)
  },
}

const getters = {
  isAuthenticated (state) {
    return !!state.currentUser
  },
}

The Vuex store or store module can have four primary elements.

state

The state is a JavaScript object that includes all the relevant data for your app.

  • It includes the main Vuex store and additional store modules.
  • You can map any part of the state into your component.
  • It is reactive. That means it will watch for changes, and your components will re-render just as you know it from data or computed values in components.
  • It can / should only be changed by a mutation.
const state = {
  isLoading: false,
  currentUser: null,
}

mutation

The mutation is a function that changes the Vuex state.

  • A mutation has to be committed, e.g. commit(SET_IS_LOADING, true).
  • It copies the existing state, mutates it, and sets it as the new current state.

They should ONLY do that!

Mutations should not use them for business logic, e.g., they should not do ajax requests or decide whether or not to change the store.

const mutations = {
  setIsLoading (state, isLoading) {
    state.isLoading = isLoading
  },
  setCurrentUser (state, user) {
    state.currentUser = user
  },
}

action

Actions are where the business logic goes.

  • They do things like fetching data from a server and setting data to a cookie or local storage.
  • They usually commit one or many mutations.
const actions = {
  async fetchCurrentUser ({ commit, state }, user) {
    // Don't do anything, it's already loading
    if (state.isLoading) {
      return
    }

    commit('isLoading', true)
    // Pseudocode
    const { user } = await graphql.request('fetchCurrentUser')
    commit('setCurrentUser', user)
    commit('isLoading', false)
  },
}

getter

Getters are like computed values in Vue components. They evaluate a return value once and cache it as long as no value in the expression changes. As soon as something changes, the getter function will be re-evaluated, and the new deal will be cached.

  • They are usually used to compute a value based on other values.
const getters = {
  // returns true if a currentUser has been set
  isAuthenticated (state) {
    return !!state.currentUser
  },
}

How to access the store?

NOTE: The following will be redundant with the Vue docs. So you might fly over it and see if you find anything that will help you further.

Every component has access to the vuex store using this.$store.

You can access the state, commit mutations, dispatch actions, and access getters using the store object.

Imagine the Vuex state to be a giant JavaScript object. Any Vue component can map any part of the state into its component.

So the components themselves are not communicating with each other. Still, they share the same data and can therefore be designed to work together without knowing each other.

Accessing state

There are three common ways how to access the state.

Example #1

// example: access state directly
const ComponentUsingStore = {
  methods: {
    logCurrentUser () {
      console.log(this.$store.state.currentUser)
    }
  }
}

Example #2

// example: mapState
import { mapState } from 'vuex'
const ComponentUsingMapState = {
  computed: {
    ...mapState({
      currentUser: state => state.currentUser
    }),
  },
  methods: {
    logCurrentUser () {
      // In this case it's `this.currentUser`
      console.log(this.currentUser)
    },
  },
}

If the same component also changes the state that it has mapped, then this might be a helpful technique.

Example #3

<template>
  <div>
    <input type="email" placeholder="Your Email" v-model="email">
  </div>
</template>

<script>
// example: have a getter and a setter
export default {
  computed: {
    email: {
      get (state) { return state.email },
      set (value) { this.$store.dispatch('setEmail', value) },
    },
  },
}
</script>

Committing mutations

I recommend not doing this, but I’ll still show you how it would work for the sake of demoing it.

You might already have put business logic into the component instead of the store.

If you do this, then Santa will keep your presents for himself.

There might be cases when to do this probably, I only do this when I want to prototype something more quickly, but when implementing a more serious solution, it’s one of the first things I create actions for.

The reasoning behind it is that mutations nor components should contain business logic when working with vuex. That’s being achieved by having vuex’s actions called mutations.

How to commit a mutation:

// example: access store directly
const ComponentCommittingMuatation = {
  methods: {
    handleEmailInput (value) {
      this.$store.commit('setEmail', value)
    },
  },
}

// example: mapMutations
import { mapMutations } from 'vuex'
const ComponentMappingMuatation = {
  methods: {
    ...mapMutations(['setEmail']),
    handleEmailInput (value) {
      this.setEmail(value)
    },
  },
}

Dispatching actions

This is very similar to mutations. The difference is that the action setEmail decide on what to commit.

// example: access store directly
const ComponentDispatchingActions = {
  methods: {
    handleEmailInput (value) {
      this.$store.dispatch('setEmail', value)
    },
  },
}

// example: mapMuatations
import { mapActions } from 'vuex'
const ComponentMappingAction = {
  methods: {
    ...mapActions(['setEmail']),
    handleEmailInput (value) {
      this.setEmail(value)
    },
  },
}

Accessing getters

Getters can either be accessed directly or mapped.

// example: access getters directly
const ComponentAccessingGettersDirectly = {
  methods: {
    doSomethingIfAuthenticated () {
      if (this.$store.getters.isAuthenticated) {
        this.doSomething()
      }
    },
  },
}

// example: mapMuatations
import { mapGetters } from 'vuex'
const ComponentMappingAction = {
  computed: {
    ...mapGetters(['isAuthenticated'])
  },
  methods: {
    doSomethingIfAuthenticated (value) {
      if (this.isAuthenticated) {
        this.doSomething()
      }
    },
  },
}

Demo App

I have created a small demo app that simulates users’ sign-in and sign-out. It’s not wired to any back-end.

Look at the code and see how different components access the same store.

Other options


Vuex should be preferred for global state management instead of this.$root or a global event bus. source: https://v2.vuejs.org/v2/style-guide/#Non-flux-state-management-use-with-caution

Event flows that depend on a component’s tree structure can be hard to reason about and are very brittle when the tree becomes large. They don’t scale well and only set you up for pain later. $dispatch and $broadcast also do not solve communication between sibling components. source: https://v2.vuejs.org/v2/guide/migration.html#dispatch-and-broadcast-replaced

  • no central state
  • will become hard to maintain
  • replace with store subscription or component watcher if state changes as a result of that event
  • only suitable for events where the state doesn’t change

https://v2.vuejs.org/v2/style-guide/#Non-flux-state-management-use-with-caution

https://alligator.io/vuejs/global-event-bus/