Vue.js communication Part 3: all components

In the last two articles we've discovered how communication works within the same component and within a parent and child setup. In this one we'll talk about how to communicate from any component to any other component. 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 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.

This 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 which 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'm going to 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 4 major 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 change the Vuex state.

  • A mutation has to be commited, e.g. commit(SET_IS_LOADING, true).
  • It makes a copy of the existing state, mutates it and sets it as the new current state.

They should ONLY do that!

They should not be used for business logic, e.g. they should not do ajax requests or decide weather or not to change the store.

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

action

Actions are where where the business logic goes.

  • They are do things like fetching data from server, setting data to a cookie or localstorage.
  • 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 evalulate 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-evalulated again and the new value will be cached.

  • They are usually being 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 just 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.

Using the store object, you can access the state, commit mutations, dispatch actions and access getters.

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 to each other but they rather share the same data and can therefore be designed to work together without having knowledge of each other.

Accessing state

There are three common ways how to access 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 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>

Comitting 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 by doing it.

If you do this then santa 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 actually 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 achived by having vuex's actions call 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 make the decision what to actually 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 a user sign in and sign out. It's not actually wired to any back-end.

Have a look at the code and see how different components access the same store.

Other options

Vue Event Bus

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

The problem is 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://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 good for events where state doesn't change

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

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