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

6

13

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?

Rosemari answered 22/9, 2020 at 12:47 Comment(1)
It is documented now. You don't need to use mapGetters. next.vuex.vuejs.org/guide/…Sussna
R
4

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>
Rosemari answered 25/9, 2020 at 16:11 Comment(0)
M
15

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.

Muttonchops answered 4/8, 2021 at 15:26 Comment(4)
i really like this solution, i am adapting it to use namespaced modules, but I am in unfamiliar waters... i have figured out how to add the module name to the mapState function, but i am not having any luck getting it to work mapGetters... any ideas?Springtail
UPDATE: gist.github.com/ub3rb3457/586467f2cbd54d0c96d60e16b247d151 this version allows you to pass the module name as an argument to the map functionSpringtail
Hey! Glad it was helpful. Don't have time to look into your adaption right now but I will at some point definitely!Sussna
Typescript version: gist.github.com/tomscytale/42cd3fca69ac876a6ab185af49815628Bentham
R
4

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>
Rosemari answered 25/9, 2020 at 16:11 Comment(0)
C
3
    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

Curriculum answered 6/7, 2022 at 13:59 Comment(1)
How would you do this with models? i.e. mapGetters('user', ['getUserName'])Tadtada
P
1

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>
Pino answered 26/1, 2022 at 18:39 Comment(1)
how is this supposed to work, im getting a linting error myNewMutation is assigned but never used. Of course, this is a lambda, where is it supposed to be called?Montage
T
1

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>
Tyrothricin answered 9/9, 2022 at 8:2 Comment(0)
R
0

You can do something like this

import { mapGetters } from "vuex"
setup() {
  return {
    ...mapGetters("myModule", ["doSomething"])
  }
}
Refit answered 8/2, 2022 at 5:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.