DEV Community

Adrian Barylski
Adrian Barylski

Posted on

Vue 3 & Vuex 4: useStoreModule composable

Now that Vue 3 and Vuex 4 official releases are around the corner, I took this opportunity to start rewriting one of my side projects in (you guessed it) Vue 3!

Vue 3 introduces the Composition API (which is also available in Vue 2 as a plugin) which opens up a whole range of possibilities. It's probably worth reading the docs first to be able understand why composables are nice and super powerful but without wasting anymore of your time, let's get into it.

This is inspired by a Github issue on the Vuex repo.


The problem

Vuex supports the idea of splitting your store into modules which is something you almost definitely want to do if you're working on a large project as it makes your state more manageable and easier to work with. Along with modules, you almost always namespace them to avoid any naming clashes by containing the module in a namespace.

In order to access any of the properties of your module in a Vue component, you will either have to use the global $store property on this object or map them as computed properties or methods which would look something like this:

<template>
  <h1>{{ greeting }}</h1>
</template>

<script>
import { createNamespacedHelpers } from 'vuex';

const { mapState, mapActions } = createNamespacedHelpers('my-module');

export default {
  computed: {
    ...mapState({
      greeting: state => state.greeting
    })
  },
  methods: {
    ...mapActions([
      'setGreeting'
    ])
  },
  mounted () {
    setTimeout(() => {
      setGreeting('Hi there ๐Ÿ‘‹');
    }, 5000)
  }
}
</script>

It's not too bad for a SFC component but you have to be very explicit in what you want to return which doesn't make this a nice DX.

The documentation for Vuex 4 is still a little vague and I didn't have enough time to investigate how to properly use Vuex in a functional component but it'll be very similar to the SFC one ๐Ÿคทโ€โ™‚๏ธ

The solution

To fix this problem, I created a little composable which hooks into Vuex's internal properties and allows you to interact with your state, actions, mutations and getters.

It can be used like this if you had one store:

import { defineComponent } from 'vue';
import { useStoreModule } from './composables';
import { State, Actions } from './state';

const Greeting = defineComponent(() => {
  const { state, actions } = useStoreModule<State, Actions>('my-module');

  setTimeout(() => {
    actions.setGreeting('Hi there ๐Ÿ‘‹');
  }, 5000);

  return () => <h1>{state.greeting}</h1>;
});

export default Greeting;

If you had multiple stores, useStoreModule accepts a second argument which would be the name of the store. So like this:

import { useStoreModule } from './composables';
import { State } from './state';

const { state } = useStoreModule<State>('my-module', 'store-name');

The useStoreModule composable is available as a gist ๐Ÿš€

Let me know if you find it helpful ๐Ÿ’ฅ ๐Ÿ‘Š

Top comments (2)

Collapse
 
tdebooij profile image
tdebooij • Edited

Hey, this seems to be exactly what I need :).

Could you provide a bit more comprehensive description of how to use this though? I'm failing to get it to work properly. What kind of types are you exporting from ./state? Right now I'm just exporting the type like this:

export const getters = typeof Getters;
Enter fullscreen mode Exit fullscreen mode

But it doesn't seem to work, in my SFC getters will just get any type and expect me to pass in the store. When ignoring that the getter in my component still will not update. The state does update though.

Collapse
 
baryla profile image
Adrian Barylski

Hey sorry about the late reply. It looks like there was a minor bug in the types for getters. If you haven't fixed it already, can you give the gist another try? Essentially, even though the types was passed through to the composable, it wasn't casted to the getters proxy function.

Before:

const getters = wrapGettersInProxy(moduleName, store.getters, isNamespaced);
Enter fullscreen mode Exit fullscreen mode

After:

const getters = wrapGettersInProxy<G>(moduleName, store.getters, isNamespaced);
Enter fullscreen mode Exit fullscreen mode