DEV Community

Rik Browning
Rik Browning

Posted on

Do you need Vuex?

With the upcoming release of Vue 3 and its much anticipated composition API, I took some time to read up on it ahead of time. I recommend reading Composition API RFC it covers everything about the API. Vue allowing us direct access to it's reactivity is very useful. One use for this could be to get rid of Vuex 😁

What's wrong with Vuex?

OK time for some random dude on the internet's opinion. Vuex is great for sharing state between components when the normal method for sharing data via props becomes cumbersome. But more often than not Vuex becomes a dumping ground for all data related to an applications state or data which doesn't need to be reactive. I don't think this would be so bad if there wasn't so much overhead for access/modifying data. Create a mutation, don't forget the getter! Oh wait that's async! Create an action to call the mutation 😅.

Probably the final nail on the coffin for me is around Typescript. I have yet to find a nice solution to interacting with Vuex, especially given it's reliance on strings for mutation and action names.

Vuex Example

Let's start with a simple example of a Vuex store.

import { Article } from "../models";
import { ActionContext } from "vuex";
import api from "../api";
import Vue from "vue";
interface ArticleState {
  articles: Article[];
}
const articleModule = {
  namespaced: true,
  state: {
    articles: []
  },
  mutations: {
    setArticles(state: ArticleState, arr: Article[]) {
      state.articles = arr;
    },
    updateArticle(state: ArticleState, a: Article) {
      const index = state.articles.findIndex(v => v.id === a.id);
      if (index > -1) {
        Vue.set(state.articles, index, a);
      }
    }
  },
  actions: {
    loadArticles(context: ActionContext<ArticleState, unknown>) {
      api.load().then(res => context.commit("setArticles", res));
    },
    like(context: ActionContext<ArticleState, unknown>, a: Article) {
      api.like(a).then(res => context.commit("updateArticle", res));
    }
  },
  getters: {
    articles(state: ArticleState) {
      return state.articles;
    },
    totalLikes(state: ArticleState) {
      return state.articles.reduce((p, c) => p + c.likes, 0);
    }
  }
};
export default articleModule;
Enter fullscreen mode Exit fullscreen mode

That's a lot of code for exposing articles, totalLikes and providing a mechanism to loadArticles and like a given article. Also has magic strings in there for committing the mutations, which I know could be extracted out to constants but yet again that's more code for a relatively simple module.

Composition API

I wanted to try and recreate this store like functionality using the new Vue composition API. Here is what I have come up with:

import { ref, computed } from "@vue/composition-api";
import { Article } from "../models";
import api from "../api";

const _articles = ref([] as Article[]);

const loadArticles = () =>
  api.load().then(articles => {
    _articles.value = articles;
  });

const like = (incoming: Article) =>
  api.like(incoming).then(res => {
    const index = _articles.value.findIndex(a => a.id === res.id);
    _articles.value.splice(index, 1, res);
  });

export default {
  articles: computed(() => _articles.value),
  totalLikes: computed(() => _articles.value.reduce((p, c) => p + c.likes, 0)),
  loadArticles,
  like
};
Enter fullscreen mode Exit fullscreen mode

Here I expose methods to act upon hidden reactive data. I then expose this reactive data via computed functions so that they are readonly. NOTE: The documentation for the API states there should be a readonly but I was unable to find a way to import it. Computed offers a similar readonly functionality.

Benefits

Here are some of the benefits I see:

  • I may be biased (read I am) but I find the composition API version a lot nicer to read and follow.
  • There is no magic strings I have to worry about.
  • Typing is obvious and less complex than having to worry about typing the overall state.
  • There are no scenarios where you have to use the Vue.set.
  • There is no pollution of the Vue prototype. This is particularly important as it means it is easier to track where this solution is being used and that we can easily tree shake/code split our solution with out having to dynamically register anything.

Pinch of salt

I have not seen the next version of Vuex that utilizes the latest composition API functionality so it may well address some of the short comings I see with it currently. With that being said, I am still not sure I will use it given I can achieve the same results with the standard composition API.

I would love to know your thoughts on using Vuex when Vue 3 is released?

Top comments (2)

Collapse
 
rleahy22 profile image
Ryan Leahy

One method I like is using reactive, and toRefs

import { reactive, toRefs, computed } from "@vue/composition-api";
import { Article } from "../models";
import api from "../api";

const state = reactive({
  articles: [] as Article[],
  totalLikes: computed(() => state.articles.reduce((p, c) => p + c.likes, 0)),
})

const loadArticles = () =>
  api.load().then(articles => {
    state.articles = articles;
  });

const like = (incoming: Article) =>
  api.like(incoming).then(res => {
    const index = state.articles.findIndex(a => a.id === res.id);
    state.articles.splice(index, 1, res);
  });

export default {
  ...toRefs(state),
  loadArticles,
  like
};
Collapse
 
kzagoris profile image
Konstantinos Zagoris

You can already apply this technique in Vue 2 using the Observable.Of. I am already doing that in my project using the class-based API. I always favor a reactive class/service for the wide-app state (for login-auth ex) for Angular using Observables, for Vue using Observable.off and local state(which can mutate freely) in each component.