Rewriting Vuex module to Composition API.
Tags: vue, vuex, javascript, typescript
Published: 2020-11-11 17:26:56

Hi there. In this post, I'll show you how to rewrite some Vuex module to the Vue Composition API. It might be a great example of how you can replace the old, good Vue state management system with this new powerful tool from the Vue 3 release.

The module comes from a simple notebook app that I've made for some workshops that I ran a while ago. You can find it [here](https://github.com/lukasborawski).

So what this module does? In short, it aggregates, saves, and removes notes. Let's look at it briefly.

```typescript
import { Module, ActionTree, GetterTree, MutationTree } from 'vuex'
import { MainState, NotesState, Note } from '~/types/store'

export const state: NotesState = {
  notes: [],
}

const mutations: MutationTree<NotesState> = {
  setNotes(state, payload: Note[]) {
    state.notes = payload
  },
}
const actions: ActionTree<NotesState, MainState> = {
  async saveNote({ state, commit }, $payload: Note) {
    commit('setNotes', [...state.notes, $payload] as Note[])
    // saving note into the storage
  },
  async removeNote({ state, commit }, $id: string) {
    commit('setNotes', notes.filter() as Note[])
    // removing notes from the storage
  },
  async getNotes({ state, commit }) {
    // getting notes from the storage
    notes.map((note: Note) => {
      commit('setNotes', [...state.notes, note] as Note[])
    })
  },
}
const getters: GetterTree<NotesState, MainState> = {
  notes: ({ notes }) => notes,
}

const namespaced: boolean = true

export const note: Module<NotesState, MainState> = {
  namespaced,
  state,
  mutations,
  actions,
  getters,
}
```

OK, for some context, we have here Typescript and some types that you can find below. In the app, there is also a `$localForage` Nuxt module that stores data locally. Check it [here](https://github.com/nuxt-community/localforage-module). For purpose of this article storing data logic will be removed.

```typescript
export interface Note {
  id: string
  title: string
  note: string
  date: Date
}
export interface MainState {
  version: string
  $localForage: any
}
export interface NotesState {
  notes: Note[] | {}[]
}
```

Now, let’s move across this module. From the top, we have - of course - a state with our notes array. Mutations keep notes saving into the state functionality. Then we have actions that add, remove, and read notes from/to storage. One getter to receive current notes at the end.

**OK, time to get our hands dirty.** 

The major and one of the most important things that  Composition API allows is to split and move our common business logic into separated blocks (files) called composables. Then reuse them in the whole app. 

So we can create one of them now. Place it into the new folder `~/composables` as a `useNotes.ts` file - we're operating with Nuxt structure. Firstly copy the types that will be used the same way as it was with Vuex module. 

In the beginning, we have to recreate the state. To do that we will use a new utility that Composition API provides called `reactive`. 

-----

*`reactive` is the equivalent of the current `Vue.observable()` API in 2.x, renamed to avoid confusion with RxJS observables. Here, the returned state is a reactive object that all Vue users should be familiar with. The essential use case for reactive state in Vue is that we can use it during render. Thanks to dependency tracking, the view automatically updates when reactive state changes.*

-----

**Tip**: check the `ref` object as well [here](https://composition-api.vuejs.org/api.html#ref).

The code:

```typescript
import {
  reactive,
  computed,
  useContext,
  ComputedRef,
} from '@nuxtjs/composition-api'
import { NotesState, Note } from '~/types/store'

const state: NoteState = reactive({
  notes: [],
})
```

One thing worth noticing is that we need to define our reactive state object outside the main composable function. We want full reactivity and access to this data from other components. We don't need to export it though.

Time for our composable `useNotes`.

In the same file we'll define this code:

```typescript
export default function useNotes(): {
  notes: ComputedRef<Note[]>
  getNotes: () => void
  saveNote: ($payload: Note) => void
  removeNote: (id: string) => void
} {
  const setNotes = ($notes: Note[]) => {
    return (state.notes = $notes)
  }
  const saveNote = async ($payload: Note) => {
    setNotes([...state.notes, $payload] as Note[])
    // saving note into the storage
  }
  const removeNote = async ($id: string) => {
    setNotes(notes.filter() as Note[])
    // removing notes from the storage
  }
  const getNotes = async () => {
    // getting notes from the storage
    notes.map((note: Note) => {
      setNotes([...state.notes, note] as Note[])
    })
  }

  return {
    notes: computed(() => state.notes),
    getNotes,
    saveNote,
    removeNote,
  }
}
```

Let's dive into it. What we have here is a simple function that returns notes from previously defined state and handlers/actions to save, remove, and get notes. Actually, they look exactly the same as the Vuex module ones. Notes are the computed value now which is delivered from Composition API and it's an equivalent of well known `computed` from Vue Options API. 

Done. We got rid of all the Vuex module complexity -  no mutations, no actions, no getters. All we need is one functional composable that can be reused anywhere we want in the app.

Additionally, we've provided some typings for returns. And as for notes handling functions is pretty straightforward, that for notes we're using now the generic type for `ComputedRef`. As of version 3 of Vue we get all the typings out of the box - awesome.

Now we can use it with the real component. In our case, it will be an `index` page. Data from the `useNotes` composable will be passed, propagated to the child components as a prop - more about chaining data through props and Composition API soon, stay tuned. 

The `index.vue` page code:

```html
<template>
  <app-notes-list :notes-prop="notes" />
</template>
```
```typescript
import useNotes from '~/composables/useNote.ts'

export default Vue.extend({
  name: 'PageIndex',
  setup() {
    const { notes, getNotes } = useNotes()
    onBeforeMount(() => {
      getNotes()
    })
    return {
      notes,
    }
  },
  components: {
    AppNotesList,
  },
})
``` 

With Vue 3 we get this new optional syntax with `setup` [function](https://composition-api.vuejs.org/api.html#setup). It allows us to combine all component logic in one place, ordered by logical blocks. The perfect scenario is that you keep your whole business code outside the component and just invoke it along with `setup` function. And as with our `index` page example, we've imported the `useNotes` composable chunks to collect notes.      

One new thing that you might remark here is this new function `onBeforeMount`. And of course, it's a hook. With Composition API there are newly redefined [hooks](https://composition-api.vuejs.org/api.html#lifecycle-hooks) that we can use with `setup` function.

And that's it. Controversial? A bit? Well, now with Composition API we could get rid of almost all Vuex complexity. From a technical perspective, it will be almost the same, but the way of defining it and acting with will be less complicated. It's just functions that we all know. We don't need mutations, actions, and getters. More we don't have to map them at all. Now just a simple import is enough and we're moving on. And the biggest advantage of the Vuex modules - separation of logic - we can still have with Composition API. One more thing might be speed and performance, but this one needs some benchmarks to confirm. Give it a try, you'll be thrilled.

The whole code is available on [this](https://github.com/lukasborawski/notebook) repo with mentioned before a simple notebook app. 

Thanks, enjoy.

<a href="https://www.buymeacoffee.com/lukas.borawski" target="_blank"><img src="https://miro.medium.com/max/1400/1*CEZSIxeYr6PCxsN6Gr38MQ.png" alt="Buy Me A Coffee"></a>