How to use Vuex mapGetters with Vue 3 SFC Script Setup syntax?

23.1k Views Asked by At

I'm refactoring component from regular Vue 3 Composition API to Script Setup syntax. Starting point:

<script lang="ts">
import { defineComponent, computed } from 'vue';
import { mapGetters } from 'vuex';

export default defineComponent({
  name: 'MyCoolBareComponent',
  computed: {
    ...mapGetters('auth', ['isAdmin']),
  },
});
</script>

Current Vue v3 migration documentation, SFC Composition API Syntax Sugar (< script setup >), links to this RFC page: https://github.com/vuejs/rfcs/pull/182

There is only one example for using computed reactive property:

export const computedMsg = computed(() => props.msg + '!!!')

As there is no current Vuex 4 documentation available that is mentioning <scrip setup>, it remains unclear to me how I should be using mapGetters when using this syntax? Or what is the correct way of going about this with Vuex 4?

6

There are 6 best solutions below

0
On BEST ANSWER

So far this syntax seems to be working. However, I'm hoping that Vuex would develop a cleaner way for exposing computed getters for template.

If you know a better way, we'd love to hear!

<script setup lang="ts">
import { mapGetters } from 'vuex';

export const name = 'MyCoolBareComponent';

export default {
  computed: {
    ...mapGetters('user', ['profile', 'roles']),
  },
};
</script>
4
On

tldr: scroll down to final result

There is now better documentation and the simple answer is: You don't need mapGetters but you can implement it yourself.

https://next.vuex.vuejs.org/guide/composition-api.html#accessing-state-and-getters

<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'

const store = useStore()

const count = computed(() => store.getters.count)
</script>

If you have many getters you want to turn into a "computed property" you could use something as "intuitive" as this:

const { countIsOdd, countIsEven } = Object.fromEntries(Object.keys(store.getters).map(getter => [getter, computed(() => store.getters[getter])]))

Put that into a function and it even looks nice.

const mapGetters = (getters) => {
  return Object.fromEntries(Object.keys(getters).map(getter => [getter, computed(() => getters[getter])]))
}

const { countIsOdd, countIsEven } = mapGetters(store.getters)

Put that function into a file and export it as a module...

// lib.js
import { computed } from 'vue'
import { useStore } from 'vuex'

const mapGetters = () => {
  const store = useStore()
  return Object.fromEntries(Object.keys(store.getters).map(getter => [getter, computed(() => store.getters[getter])]))
}

export { mapGetters }

...and you can easily use it in all your components.

// components/MyComponent.vue
<script setup>
import { mapGetters } from '../lib'

const { countIsOdd, countIsEven } = mapGetters()
</script>

Final result:

Here's the final lib.js I came up with:

import { computed } from 'vue'
import { useStore } from 'vuex'

const mapState = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store.state).map(
      key => [key, computed(() => store.state[key])]
    )
  )
}

const mapGetters = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store.getters).map(
      getter => [getter, computed(() => store.getters[getter])]
    )
  )
}

const mapMutations = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store._mutations).map(
      mutation => [mutation, value => store.commit(mutation, value)]
    )
  )
}

const mapActions = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store._actions).map(
      action => [action, value => store.dispatch(action, value)]
    )
  )
}

export { mapState, mapGetters, mapMutations, mapActions }

Using this in the component looks like this:

<template>
  Count: {{ count }}
  Odd: {{ counterIsOdd }}
  Even: {{ counterIsEven }}
  <button @click="countUp">count up</button>
  <button @click="countDown">count down</button>
  <button @click="getRemoteCount('https://api.countapi.xyz')">
    get remote count
  </button>
</template>

<script setup>
import { mapState, mapGetters, mapMutations, mapActions } from '../lib'

// computed properties
const { count } = mapState()
const { countIsOdd, countIsEvent } = mapGetters()

// commit/dispatch functions
const { countUp, countDown } = mapMutations()
const { getRemoteCount } = mapActions()
</script>

Any feedback on this would be very appreciated.

1
On

You don't need to export anything, an SFC will register all variables and components for you and make them available in template.

An SFC automatically infers the component's name from its filename.

Here are a few examples that may be useful:

<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'

import MyComponent from './components/MyComponent'

const store = useStore()

const data = 'Random string as a data'

// without module/data 
const myAction = () => store.dispatch('myAction')
// with data
const mySecondAction = () => store.dispatch('mySecondAction', data)

// with module
const myMutation = () => store.commit('moduleName/myMutation')
// with module/data
const myNewMutation = () => store.commit('moduleName/myNewMutation', data)

const myStateVariable = computed(() => store.state.myStateVariable)
// with module
const myGetter = computed(() => store.getters.moduleName.myGetter)

// replace using of mapState/mapGetters
const state = computed(() => store.state)

// and then
console.log(state.myStateVariable)
console.log(state.mySecondStateVariable)
....

</script>
1
On
    import {useStore} from "vuex";
    import {computed} from "vue";
    
    const {getEvents, getSelectedTag} = useStore().getters;
    const events = computed(() => getEvents)
    const selectedTag = computed(() => getSelectedTag)

i do this and for me is working

0
On

You can do something like this

import { mapGetters } from "vuex"
setup() {
  return {
    ...mapGetters("myModule", ["doSomething"])
  }
}
0
On

Follow this:
https://vuex.vuejs.org/guide/typescript-support.html#typing-usestore-composition-function

Here is an example:

  • store.ts
import { InjectionKey } from 'vue'
import { createStore, Store } from 'vuex'

// define your typings for the store state
export interface State {
  token: string|null
}

// define injection key
export const key: InjectionKey<Store<State>> = Symbol()

export const store = createStore<State>({
  state: {
    token: localStorage.getItem('token') ? localStorage.getItem('token'):'',
  }
})
  • main.js
import { store, key } from './store'
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// pass the injection key
app
.use(store, key)
.mount('#app')
  • In a vue component
<script setup>
import { onMounted } from 'vue'
import { useStore } from 'vuex'
import { key } from './store'

const token = useStore(key) 

onMounted(() => {
  console.log(store.state.token)
})

</script>