danielroe/typed-vuex

feat: Subscribe to store inside a component

wobsoriano opened this issue · 5 comments

Hi, thanks for this great plugin. I've been using it and pardon for not following the format.

Is there a way to subscribe to the store in a component? Something like this in options API:

  created() {
    this.$store.subscribe((mutation, state) => {
      if (mutation.type === 'snackbar/SHOW_MESSAGE') {
        this.message = state.snackbar.content;
        this.color = state.snackbar.color;
        this.show = true;
      }
    });
  }

I'm using vuetify currently and I had to create a plugin for a reusable snackbar since I cannot directly put a snackbar model in a store (mutation errors), so I had to subscribe to it and set the values locally.

Any help would be appreciated. Thanks.

@sorxrob This should still work. But I take it you mean - to subscribe to a store via the accessor, in a typed way?

@sorxrob This should still work. But I take it you mean - to subscribe to a store via the accessor, in a typed way?

Yes, sorry the post was incomplete. In a typed way.

My index.ts file contains

import { getAccessorType } from 'nuxt-typed-vuex';

import * as auth from './auth';
import * as snackbar from './snackbar';

export const accessorType = getAccessorType({
  modules: {
    auth,
    snackbar,
  },
});

@sorxrob This doesn't answer your question, but may help with your implementation. I also use vuetify with a vuex controlled snack component. The following code has not been tested but should give you an idea of how to implement it - the golden ticket is in the computed prop for show in the snack component.

store/snackControl.ts

import { getterTree, mutationTree } from 'typed-vuex'

export interface snackControlState {
  message: string
  color: string
  show: boolean
}

export const state = (): snackControlState => ({
  message: '',
  color: 'primary',
  show: false
})

export const mutations = mutationTree(state, {
  SHOW_MESSAGE(state, options: snackControlState) {
    Object.assign(state, options)
  },
  SET_SHOW(state, v: boolean) {
    state.show = v
  }
})

export const getters = getterTree(state, {
  getMessage: state => state.message,
  getColor: state => state.color,
  getShow: state => state.show
})

store/index.ts

import { getAccessorType } from 'typed-vuex'
import * as snackControl from '~/store/snackControl'

export const accessorType = getAccessorType({
  modules: {
    snackControl
  }
})

snack.vue

<template lang="html">
  <v-snackbar v-model="show" :timeout="2000" top :color="color">
    {{ message }}
  </v-snackbar>
</template>

<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  computed: {
    show: {
      get() {
        return this.$accessor.snackControl.getShow
      },
      set(v: boolean) {
        this.$accessor.snackControl.SET_SHOW(v)
      }
    },
    color() {
      return this.$accessor.snackControl.getColor
    },
    message() {
      return this.$accessor.snackControl.getMessage
    }
  },
  methods: {
    // Use this method in a different page/component to show the snack
    showSnackMessage() {
      this.$accessor.snackControl.SHOW_MESSAGE({ message: 'Hello snack', color: 'warning', show: true })
    }
  }
})
</script>

<style lang="scss" scoped></style>

@JamieCurnow awesome, will try this out. Thanks!

@JamieCurnow thanks for the tip, indeed that works!

snackbar.ts

export const state = () => ({
  show: false,
  content: '',
  color: '',
});

export const mutations = mutationTree(state, {
  [MutationTypes.SHOW_MESSAGE](state, payload: ISnackbar) {
    Object.assign(state, payload);
  },
  [MutationTypes.SET_SHOW](state, payload: boolean) {
    state.show = payload;
  },
});

export const getters = getterTree(state, {
  getContent: (state) => state.content,
  getColor: (state) => state.color,
  getShow: (state) => state.show,
});

snackbar.vue

<template>
  <v-snackbar v-model="show" :color="color">
    {{ message }}
    <template v-slot:action="{ attrs }">
      <v-btn text v-bind="attrs" @click="show = false">Close</v-btn>
    </template>
  </v-snackbar>
</template>

<script lang="ts">
import { defineComponent, computed } from '@vue/composition-api';
export default defineComponent({
  setup(_props, { root }) {
    const show = computed({
      get: () => root.$accessor.snackbar.getShow,
      set: (v: boolean) => root.$accessor.snackbar.SET_SHOW(v),
    });
    const message = computed(() => root.$accessor.snackbar.getContent);
    const color = computed(() => root.$accessor.snackbar.getColor);

    return {
      show,
      message,
      color,
    };
  },
});
</script>