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?

1

6 Answers 6

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.

Sign up to request clarification or add additional context in comments.

4 Comments

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?
UPDATE: gist.github.com/ub3rb3457/586467f2cbd54d0c96d60e16b247d151 this version allows you to pass the module name as an argument to the map function
Hey! Glad it was helpful. Don't have time to look into your adaption right now but I will at some point definitely!
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>

Comments

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

1 Comment

How would you do this with models? i.e. mapGetters('user', ['getUserName'])
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>

1 Comment

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?
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>

Comments

0

You can do something like this

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.