Olasunkanmi John Ajiboye TypeScript and Rust enthusiast. Writes code for humans. From the land of Promise.

What’s new in Vuex 4

5 min read 1489

What's New In Vuex 4

Introduction

Over the years, frontend applications have become increasingly complex and stratified. As more and more data is being handled in the frontend, perhaps the most important challenge is managing the various complex states a client application can be in. This is why state management is perhaps the most critical challenge in building robust frontend applications.

In the past few years, Redux, MobX, and Vuex have emerged as the most popular libraries for state management in the JavaScript ecosystem. Despite Redux probably being the most popular of them, Vuex has cemented its place as the de facto state management library for web applications built on Vue.js, primarily due to its relative simplicity and its integration with Vue.

Why Vuex?

Both Redux and Vuex are inspired by the Flux architecture. However, the major difference is in how they handle state. In Redux, which is more popular with React developers, state is absolutely immutable; with Vuex, you have specific mutation rules that are much more approachable, less verbose, and more intuitive.

Vue components get their state from the Vuex store, which is essentially an object, but with distinct characteristics from a plain JavaScript object: they are reactive, and you cannot directly mutate the store’s state. The only way to change a store’s state is by explicitly committing mutations.

So, what’s new?

Vuex has gone through quite a number of iterations to get to its current state. In this article, we’d focus on what’s new in the latest version of VueX, version v4.0.0-beta.

The major goals of version 4 are to support the new Composition API introduced in Vue 3 and to simplify the usage of Vuex overall. It is also intended to support a more robust inference for TypeScript. We will explore each of these in detail with few examples.

Breaking changes

As of the time of writing, VueX@v4.0.0beta had just been released. One of the major breaking changes is the removal of the global typings for this.$store within Vue components. The aim of this feature is to enable typescript users to compose full typing layers in components. Hence, developers can do manual declarations that will enable fully typed structures that were nearly impossible in Vuex 3.

In summary, according to the release notes:

When using TypeScript, you must provide your own augment declaration.

Here’s the example provided in the release notes above:

// vuex-shim.d.ts
declare module "@vue/runtime-core" {
  // Declare your own store states.
  interface State {
    count: number
  }
  interface ComponentCustomProperties {
    $store: Store<State>;
  }
}

We will explore this in deeper detail next.

We made a custom demo for .
No really. Click here to check it out.

State

The definition of a store typically starts with defining the state:

interface Actor {
  name: string
  age: number
  }
interface State {
  loading: boolean
  data: Array<Actor>
}
export const state:State = {
  loading: false,
  data: [{name: 'John',age: 25}]

It is important to export the type of state because it will be used in the definitions of getters, as well as mutations and actions. There is an accompanying GitHub repo for the examples in this post here.

Mutations

Just like in Redux and other Flux implementations, it is common to store mutations as constants. The approach here would be to store all our potential mutations as a MutationTypes enum.

// mutation-types.ts:
export enum MutationTypes {
  SET_LOADING = 'SET_LOADING',
  FETCH_ACTORS = 'FETCH_ACTORS',
  ADD_ACTOR = 'ADD_ACTOR',
  REMOVE_ACTORS = 'REMOVE_ACTOR'
}

We can now declare a “contract” for each of our mutations. This is a common pattern in Redux, analogous to reducers. A mutation is simply a function that accepts the state and an optional payload, mutates the state, and returns the newly updated state.

With our newfound knowledge, let’s declare a type for our mutation. We have a couple of options for doing this; the more robust way is to employ TypeScript’s generic for typing our mutation:

// mutuations.ts
import { MutationTypes } from './mutation-types'
import { State } from './state'
export type Mutations<S = State> = {
  \[MutationTypes.SET_LOADING\](state: S, payload: boolean): void
}

Looking good so far? Now let’s write an implementation:

import { MutationTree } from 'vuex'
import { MutationTypes } from './mutation-types'
import { State,Actor } from './state'
export type Mutations<S = State> = {
  \[MutationTypes.SET_LOADING\](state: S, payload: boolean): void,
  \[MutationTypes.REMOVE_ACTORS\](state: S, payload: Actor): Array<Actor>,
  \[MutationTypes.ADD_ACTOR\](state: S, payload: Actor): Array<Actor>,
}
export const mutations: MutationTree<State> & Mutations = {
    \[MutationTypes.SET_LOADING\](state, payload: boolean) {
      state.loading = payload
      return state
    },
  \[MutationTypes.REMOVE_ACTORS\](state, payload: Actor) {
    state.data = [payload]
    return state.data
  },
  \[MutationTypes.ADD_ACTOR\](state, payload: Actor) {
    state.data = [payload]
    return state.data
  }
}

The mutations store all potential implemented mutations. This will be eventually used to construct the store.

Actions

Let’s write a simple action for our application. First, let’s type the action type with an enum:

// action-types.ts
export enum ActionTypes {
  GET_ACTOR = 'GET_ACTORS',
}
```
`actions.ts`
```
import { ActionTypes } from './action-types'
export const actions: ActionTree<State, State> & Actions = {
  async \[ActionTypes.GET_ACTORS\]({ commit }) {
   const allActors = await fetch('actorsAPI').then((actors)=> commit(MutationTypes.ADD_ACTOR,actors))
   return allActors
  },
}

Getters

Getters can also be statically typed. A getter isn’t much different from a mutation, as it is essentially a function that receives a state and performs a computation on it.

The below showcases an example that takes the state as the first argument and returns the actor name in uppercase. This can get really complex, but it follows the same basic principles:

// getters.ts
import { GetterTree } from 'vuex'
import { State } from './state'
export type Getters = {
  capitalizeName(state: State): string[]
}
export const getters: GetterTree<State, State> & Getters = {
  capitalizeName: (state) => {
    return state.data.map(actor => actor.name.toUpperCase())
  },
}

Global $store type

As mentioned earlier, you now have to explicitly type your store to access them in your components. So all default Vuex types — getters, commit, and dispatch — will be replaced by our custom types.

To make our defined types globally accessible and correctly working, we need to pass them to Vue, like so:

// vuex-shim.d.ts
import {State} from '../state'
declare module "@vue/runtime-core" {
    // Declare your own store states.
    interface State {
      count: number
    }

    interface ComponentCustomProperties {
      $store: Store<State>;
    }
  }

Usage in components

Now that we have a typed store, let’s utilize it in a component to solidify the concepts. We will be looking at usage in a component with the Composition API syntax since this is a major for change for Vue and one of the core purpose in Vuex 4.

Composition API

We must access the store through the useStore hook with the Composition API. The useStore hook essentially returns our store:

export function useStore() {
  return store as Store
}


<script lang="ts">
import { defineComponent, computed, h } from 'vue'
import { useStore } from '../store'
import { MutationTypes } from '../store/mutation-types'
import { ActionTypes } from '../store/action-types'
export default defineComponent({
  name: 'CompositionAPIComponent',
  setup(props, context) {
    const store = useStore()
    const actors = computed(() => store.state.data)
    const capitalizeActors = computed(() => store.getters.capitalizeName)
    async  function removeActor() {
      store.commit(MutationTypes.REMOVE_ACTORS,  {name: 'John',age: 67})
    }

    async function addActor() {
      const result = await store.dispatch(ActionTypes.GET_ACTORS, {name: 'John',age: 67})
      return result
    }
    return () =>
      h('section', undefined, [
        h('h2', undefined, 'Composition API Component'),
        h('p', undefined, actors.value.toString()),
        h('button', { type: 'button', onClick: addActor }, 'Add Actor'),
             h('button', { type: 'button', onClick: removeActor}, 'Remove Actor'),
      ])
  },
})
</script>

What we get is a fully statically typed store. We can only commit/dispatch declared mutations/actions with appropriate payloads; otherwise, we get an error.

This is fantastic for static analysis, which is great for self-documenting code. If you tried to send a malformed payload or an uninitiated action you have the TypeScript compiler yelling at you and guiding you to correctness. That’s awesome, isn’t it?

Conclusion

We have looked at what’s new and what’s in the pipeline for Vuex 4. At the time of writing, v4.0.0-beta.1 is the only version of Vuex 4 with major feature releases with a breaking change — that is, robust typing for the store and excellent integration with the Composition API in Vue 3.

v4.0.0-beta.3 also comes with a major feature: the createLogger function was exported from Vuex/dist/logger, but it’s now included in the core package. You should import the function directly from the Vuex package. This is a small but important feature to take note of.

It is imperative to remember that Vuex 4 is still in beta and is thus tagged as pre-release, implying that we can expect more exciting changes that have not been fully implemented or released. These include getting rid of mapXXX and eliminating the need to separate actions and mutations.

It is such an exciting time to be a fan and a user of Vue and Vuex . Be on the lookout for the official release of Vuex 4 with all the amazing new features — I am particularly delighted with enhanced static typing. Once again here is an accompanying GitHub repo for the code examples.

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - .

Olasunkanmi John Ajiboye TypeScript and Rust enthusiast. Writes code for humans. From the land of Promise.

2 Replies to “What’s new in Vuex 4”

  1. Your action ActionTypes.GET_ACTORS does not require payload. Why you give payload, when calling it ? 🙂

Leave a Reply