Skip to content

🧰 A VueX wrapper to fully type your modules without boilerplate!

Notifications You must be signed in to change notification settings

victorgarciaesgi/vuex-typed-modules

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🧰 vuex-typed-modules

npm version npm downloads npm downloads

A VueX 3 & 4 wrapper that provides type safe hooks and handlers to your Vuex Store modules with less code!

4.x Support Vue 2 & 3, and Vuex 3 & 4 and added composition-api support (Thanks to vue-demi)

3.x Working with Vue 2, and Vue 3 with Vuex4 (but not hooks)

Installation

npm i vuex-typed-modules
#or
yarn add vuex-typed-modules

Usage for version 4.x

Define Module and hook

import { createVuexModule } from 'vuex-typed-modules';

export const [testModule, useTestModule] = createVuexModule({
  name: 'testModule',
  state: {
    count: 1,
  },
  mutations: {
    addCount(state, number: number) {
      state.count += number;
    },
  },
  actions: {
    async addCountAsync(_, count: number): Promise<void> {
      await myAsyncFunction(count);
      // Calling mutation
      testModule.mutations.addCount(count);
    },
  },
});

Module declaration (For 4.x and 3.x)

In your main.ts

import { Database } from "vuex-typed-modules";
import { testModule } from '~modules'

const database = new Database({ logger: true });
const store = new Vuex.Store({
  plugins: [database.deploy([testModule])];
})

// If you're using Vue 3:
createApp(App).use(store).mount("#app");

// If you're using Vue 2:
new Vue({
  store,
  render: h => h(App)
}).$mount("#app");

For Nuxt.js

// store/index.ts
import { Database } from 'vuex-typed-modules';
import { testModule } from '~modules';

const database = new Database({ logger: process.browser });
export const plugins = [database.deploy([testModule])];

export const state = () => ({});

Usage in your components or in other modules!

<template>
  <div class="hello">
    {{ count }}
    <button @click="increment">increment</button>
  </div>
</template>
import { defineComponent, onBeforeUnmount } from 'vue';
import { testModule } from '~/modules';

export default defineComponent({
  name: 'Home',
  setup() {
    const {
      state: { count },
      actions: { increment },
    } = useChildStoreModule();

    return {
      count,
      increment,
    };
  },
});

Note

Store hooks return refs for the state. It can be overwridden by using the option unwrap

With Refs

// You can destructure state when using refs
const {
  state: { count },
} = useTestModule(); // count is of type `Ref<number>`
console.log(count.value);

Without Refs

// If you destructure the state, it will loses reactivity
const { state } = useTestModule({ unwrap: true });
state.count; // count is of type `number`

Dynamic Modules

For dynamic modules, simply use the class VuexDynamicModule instead

import { createVuexDynamicModule } from 'vuex-typed-modules';

export const dynamicModule = createVuexDynamicModule({
  name: 'dynamicModule',
  logger: false,
  state: {
    count: 1,
    type: null,
  },
  actions: {
    // Due to limitions of Typescript, I can't provide typings for infered mutations and getters inside the same object.
    // It would make an infinite loop (I tried).
    // For dynamic module you can fallback on "commit" "dispatch" and "getters"
    exemple({ state, commit, dispatch, getters }, param: string) {
      // ...
    },
  },
});

Usage

<script lang="ts">
import { defineComponent, onBeforeUnmount } from 'vue';
import { dynamicModule } from '~/modules';

const [ChildStoreModule, useChildStoreModule] = dynamicModule.instance('child-store');
// Dot not declare it in other files, only import it from here

export default defineComponent({
  name: 'TestView',
  setup() {
    ChildStoreModule.register();
    const {
      state: { count },
    } = useChildStoreModule();

    onBeforeUnmount(() => {
      ChildStoreModule.unregister();
    });

    return {
      count,
    };
  },
});
</script>

Default module helpers

Vuex types modules also add helpers functions on top of your module to prevent from writing short mutations

testModule.resetState();
// Reset your module to the initial State
// You can specify only the property you want to update
testModule.updateState({
  count: 3,
});

// You can also give a callback function to have access to the current state
testModule.updateState((state) => ({
  count: state.count + 2,
}));

// And also mutate the state directly (A bit heavier on the update)
testModule.updateState((state) => {
  state.count++;
});

-------------------------------------------

Usage for 3.x

Define Module

Create a test.module.ts in your store folder

import { VuexModule } from 'vuex-typed-modules';

export const testModule = new VuexModule({
  name: 'testModule',
  state: {
    count: 1,
  },
  mutations: {
    addCount(state, number: number) {
      state.count += number;
    },
  },
  actions: {
    async addCountAsync(_, count: number): Promise<void> {
      await myAsyncFunction(count);
      // Calling mutation
      testModule.mutations.addCount(count);
    },
  },
});

Usage in your components or in other modules!

<template>
  <div class="hello">
    {{ count }}
    <button @click="increment">increment</button>
  </div>
</template>
import { Component, Prop, Vue } from 'vue-property-decorator';
import { testModule } from '~/modules';

@Component
export default class Home extends Vue {
  get count() {
    return testModule.getters.count;
  }

  async increment() {
    await testModule.actions.addCountAsync(2);
  }
}

Tests

// user.spec.ts
import { createTestStore } from 'vuex-typed-modules';
import { createLocalVue } from '@vue/test-utils';
// import state, actions, mutations and getters from some store module

const configStore = {
  state,
  actions,
  mutations,
  getters,
};

const localVue = createLocalVue();
localVue.use(Vuex);

const store = createTestStore(configStore);

describe('User Module', () => {
  it('name should be empty', () => {
    expect(store.state.name).toBe('');
  });
  it('name should change to Foo', () => {
    store.dispatch('CHANGE_NAME', 'Foo');
    expect(store.state.name).toBe('Foo');
  });
});