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

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?

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.

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>

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

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>

You can do something like this
import { mapGetters } from "vuex"
setup() {
return {
...mapGetters("myModule", ["doSomething"])
}
}

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>

Related

How to use $store.commit in Nuxt with #vue/composition-api

<template>
<div>
<h1>Vuex Typescript Test</h1>
<button #click="handleLogin">click</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from '#vue/composition-api'
export default defineComponent({
setup() {
return {
handleLogin() {
// something....
},
}
},
})
</script>
#vue/composition-api do not apply useStore
I want to use store in setup function.
You should be able to access the useStore composable in the setup function according to the documentation of Vuex.
Your script section will look like this:
import { defineComponent } from '#vue/composition-api';
import { useStore } from 'vuex';
export default defineComponent({
setup() {
return {
const store = useStore();
return {
handleLogin {
store.dispatch('auth/login');
},
};
}
},
});
The proper way to structure the content of setup would be to move the handleLogin as a separate const and expose the constant in the return, in order to keep the return section more readable like this:
setup() {
const store = useStore();
const handleLogin = () => {
store.dispatch('auth/login');
};
return {
handleLogin,
}
}

Vuex useStore is giving me undefined in this component

<template>
<div :style="styles">
<Spinner v-if="showSpinner" class="w-14 h-14" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Spinner from '../common/Spinner.vue';
export default defineComponent({
name: 'FullQuestionnaire',
components: { Spinner },
});
</script>
<script setup lang="ts">
import { withDefaults, defineProps, computed, ref, toRefs, watch } from 'vue';
import { useStore } from 'vuex';
const props = withDefaults(
defineProps<{
isLoading?: boolean;
additions: AdditionalParameters;
questionnaireConfig: QuestionnaireConfig;
questionnaireStyling?: RawQuestionnaireStyles | null;
submitAnswers: SubmitAnswersFunction;
goBack: () => void;
}>(),
{ isLoading: undefined, questionnaireStyling: null }
);
const store = useStore();
const redirectUrl = computed(
() => thankYouScreenInfo.value.properties.redirect_url ?? ''
);
const processedRedirectUrl = computed(() => {
const url = populatePlaceholdersInLinkQueryParameters(
redirectUrl.value,
''
// store.getters.userProfile
);
});
</script>
Hi, I am new to Vue and still learning. As you can see in the code I am importing useStore from vuex but when I tried to console.log(store) after this declaration const store = useStore(); its giving me undefined.
Vue Document
When I look into the vuex documentation. the example is using the useStore when setting up. Can I call const store = useStore(); outside of the setup?
Why is that?

Pinia getter does not filter state (options API)

I have a pinia store I am using with a vue component, with the options API. I have a getter in my pinia store that is supposed doing some basic filtering of items. However, the getter just returns what is in state without any of the filtering applied.
My component:
<template>
<DetailsWrapper :filteredDetails="filteredDetails"
</template>
<script>
import {mapState, } from 'pinia';
export default {
components: DetailsWrapper,
computed: {
...mapState(useDetailsStore, {
filteredDetails: store => store.filteredDetails,
},
};
</script>
In my pinia store I have:
import axios from 'axios';
import { defineStore } from 'pinia';
const useDetailsStore = defineStore('details', {
getters: {
filteredDetails: state => {
const productDetails = state.product && state.product.details;
productDetails.forEach(detail => {
detail.values.filter(detail => detail.isOnline && detail.isDisplayable
});
return productDetails
},
});
export default useDetailsStore
The end result is just that everything in productDetails is returned -- nothing is filtered out, even though there are definitely values to be filtered.
If anyone could provide any guidance it would be much appreciated!
You can try like this:
import axios from 'axios';
import { defineStore } from 'pinia';
const useDetailsStore = defineStore('details', {
getters:
{
filteredDetails(state)
{
return (state.product?.details || []).map(product => ({
...product,
values: product.values.filter(val => val.isOnline && val.isDisplayable),
});
},
});
export default useDetailsStore;

How to use Pinia with defineCustomElement in vue 3

is it possible to use the pinia store in a component as an element?
try doing it this way but i get the following error in developer console
index.8ec3cfca.js:1 TypeError: Cannot read properties of undefined (reading '_s')
pinia store
import { UsuarioInternet } from "../../models/UsuariosInternet.model"
import { defineStore } from "pinia";
export const useFAUsuarioInternet = defineStore("useFAUsuarioInternet",{
state:() => ({
items: <UsuarioInternet[]>([])
}),
getters:{
listaUsuario(state){
return state.items;
}
},
actions:{
createNewUser(item :UsuarioInternet){
if(!item) return;
this.items.push(item);
},
findIndexById(id: number){
return this.items.findIndex((item) => item.id == id);
}
}
})
component
<template>
<p>
setsetsetestset
</p>
</template>
<script lang="ts" setup >
import { ref , onMounted, computed} from 'vue';
import { useFAUsuarioInternet } from "../stores/general/useFAUsuariosInternet";
import { UsuarioInternet } from "../models/UsuariosInternet.model";
let Usuario = ref<UsuarioInternet>(new UsuarioInternet);
//mounted
onMounted(() => {
});
const mainStore = useFAUsuarioInternet();
//call action
const saveUser = () => {
mainStore.createNewUser(Usuario.value);
}
//getters
const lista = computed(() => mainStore.listaUsuario)
</script>
Main.ts
import { createApp, defineCustomElement } from 'vue'
import UsuariosInternet from './pages/general/UsuariosInternet.ce.vue'
import homeTest from './components/homeTest.ce.vue'
const element = defineCustomElement(UsuariosInternet);
customElements.define("usuarios-internet", element);
const element2 = defineCustomElement(homeTest);
customElements.define('home-test', element2);
Here's the main.ts you want:
import { defineCustomElement } from "vue";
import { createPinia, setActivePinia } from "pinia";
import UsuariosInternet from "./pages/general/UsuariosInternet.ce.vue";
import homeTest from "./components/homeTest.ce.vue";
// Here you create a standalone pinia
setActivePinia(createPinia());
const element = defineCustomElement(UsuariosInternet);
customElements.define("usuarios-internet", element);
const element2 = defineCustomElement(homeTest);
customElements.define("home-test", element2);
This will create a global store for all your components.
See: https://github.com/vuejs/pinia/discussions/1085#discussioncomment-2219587

How to get current name of route in Vue?

I want to get the name of the current route of vue-router, i have a component menu with navigation to another componentes, so i want to dispaly the name of the current route.
I have this:
created(){
this.currentRoute;
//this.nombreRuta = this.$route.name;
},
computed:{
currentRoute:{
get(){
this.nombreRuta = this.$route.name;
}
}
}
But the label of the name of the route does not change, the label only show the name of the first loaded route.
Thank You
EDIT:
Image to show what i want
You are using computed incorrectly. You should return the property in the function. See the docs for more information.
Here is your adapted example:
computed: {
currentRouteName() {
return this.$route.name;
}
}
You can then use it like this:
<div>{{ currentRouteName }}</div>
You can also use it directly in the template without using a computed property, like this:
<div>{{ $route.name }}</div>
Vue 3 + Vue Router 4
Update 5/03/2021
If you are using Vue 3 and Vue Router 4, here is two simplest ways to get current name of route in setup hook:
Solution 1: Use useRoute
import { useRoute } from 'vue-router';
export default {
setup () {
const route = useRoute()
const currentRouteName = computed(() => route.name)
return { currentRouteName }
}
}
Solution 2: Use useRouter
import { useRouter } from 'vue-router';
export default {
setup () {
const router = useRouter()
const currentRouteName = computed(() => router.currentRoute.value.name;)
return { currentRouteName }
}
}
I use this...
this.$router.history.current.path
In Composition API, this works
import { useRouter } from 'vue-router'
const router = useRouter()
let currentPathObject = router.currentRoute.value;
console.log("Route Object", currentPathObject)
// Pick the values you need from the object
I used something like this:
import { useRoute } from 'vue-router';
then declared
const route = useRoute();
Finally if you log route object - you will get all properties I used path for my goal.
This is how you can access AND watch current route's name using #vue/composition-api package with Vue 2 in TypeScript.
<script lang="ts">
import { defineComponent, watch } from '#vue/composition-api';
export default defineComponent({
name: 'MyCoolComponent',
setup(_, { root }) {
console.debug('current route name', root.$route.name);
watch(() => root.$route.name, () => {
console.debug(`MyCoolComponent- watch root.$route.name changed to ${root.$route.name}`);
});
},
});
</script>
I will update this answer once Vue 3.0 and Router 4.0 gets released!
I use this...
this.$route.name
In my Laravel app I created a router.js file and I can access the router object in any vue component like this.$route
I usually get the route like this.$route.path
Using composition API,
<template>
<h1>{{Route.name}}</h1>
</template>
<script setup>
import {useRoute} from 'vue-router';
const Route = useRoute();
</script>
Using Vue 3 and Vue Router 4 with Composition API and computed:
<script setup>
import { computed } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// computed
const currentRoute = computed(() => {
return router.currentRoute.value.name
})
</script>
<template>
<div>{{ currentRoute }}</div>
</template>
⚠ If you don't set a name in your router like so, no name will be displayed:
const routes = [
{ path: '/step1', name: 'Step1', component: Step1 },
{ path: '/step2', name: 'Step2', component: Step2 },
];
In Vue 3.2 using Composition API
<script lang="ts" setup>
import { useRoute } from "vue-router";
const route = useRoute();
const currentRouteName = computed(() => {
return route.name;
});
</script>
<template>
<div>
Using computed:{{currentRouteName}}
or without using computed: {{route.name}}
</div>
</template>
This is how you can get id (name) of current page in composition api (vue3):
import { useRoute } from 'vue-router';
export function useFetchPost() {
const currentId = useRoute().params.id;
const postTitle = ref('');
const fetchPost = async () => {
try {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/posts/${currentId}`
);
postTitle.value = response.data.title;
} catch (error) {
console.log(error);
} finally {
}
};
onMounted(fetchPost);
return {
postTitle,
};
}
I'm using this method on vue 3 & vue-router 4
It works great!
<script>
import { useRoute } from 'vue-router'
export default {
name: 'Home',
setup() {
const route = useRoute();
const routeName = route.path.slice(1); //route.path will return /name
return {
routeName
}
}
};
</script>
<p>This is <span>{{ routeName }}</span></p>
I've Tried and it Worked:
Use Following in Your Elements;
{{ this.$route.path.slice(1) }}
this.$router.currentRoute.value.name;
Works just like this.$route.name.
Vue 3 + Vue Router 4 + Pinia store (or any other place outside of vue components)
#KitKit up there gave an example how to get route if you are using Vue 3 and Vue Router 4 in setup hook. However, what about state management in Pinia store ?
In vue#2 and vue-router#3.5.1: We could have used router.currentRoute.query.returnUrl like so (example in vuex state management):
import router from "#/router";
const state = initialState;
const getters = {};
const actions = { // your actions };
const mutations = {
loginSuccess(state, user) {
let returnUrl = "";
if(router.currentRoute.query.returnUrl != undefined)
returnUrl = router.currentRoute.query.returnUrl;
},
};
export default {
state,
getters,
actions,
mutations,
};
export const authentication = {
actions: {},
mutations: {},
};
In vue#3 and vue-router#4: We have to append value to currentRoute like so:
import router from '#/router';
export const authenticationStore = defineStore('authUser', {
state: (): State => ({
// your state
}),
getters: {
// your getters
},
actions: {
loginSuccess(user: object) {
let returnUrl = '';
if (router.currentRoute.value.query.returnUrl != undefined)
returnUrl = router.currentRoute.value.query.returnUrl;
},
},
});