Trying to pass route query to axios request, but it is empty..
route.query returns empty in mounted. route.queryreturns {"filter[city]": "Vilnius" } in axios then
nextTick doesn't solve issue. Any tips?
import { ref, onMounted, nextTick } from 'vue';
import axios from 'axios';
import { useRouter, useRoute } from 'vue-router';
export default {
setup() {
const router = useRouter();
const route = useRoute();
onMounted(() => {
console.log(route.query); // log is {}
fetchApartments();
});
function fetchApartments() {
console.log(route.query); // log is {}
axios.get('/api/apartments').then(response => {
console.log(route.query); // log is { "filter[city]": "Vilnius" }
});
}
}
}
Route navigation is asynchronous. You need to wait for router.isReady for queries to be available
import {useRouter, useRoute} from 'vue-router';
export default {
setup() {
const router = useRouter();
const route = useRoute();
onMounted(async () => {
await router.isReady();
console.log(route.query);
});
}
}
Update your code like this:
...
import { computed } from 'vue'
...
and inside setup()
const route = useRoute();
const query = computed(() => route.query)
The missing part here is computed property.
Related
I want to get users in threadStore from userStore, but users is undefined. And I have no idea why.
Also in userStore threads from threadStore has a correct value.
Maybe that because threadStore installed first than userStore?
But how I can get users from userStore in threadStore?
log image
thread.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { useUserStore } from './user';
export const useThreadStore = defineStore('thread', () => {
const userStore = useUserStore();
console.log(userStore.users); // is undefined
const threads = ref([]);
return {
threads,
}
user.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { useThreadStore } from './thread';
export const useUserStore = defineStore('user', () => {
const threadStore = useThreadStore();
console.log(threadStore.threads); // has a correct value
const users = ref([]);
return {
users,
};
});
I'm using Vuejs 3, vuerouter 4 and pinia and trying to put a navigation guard in some routes, as per the example in the documentation (if a user is not authenticated and is not on the login page, send the user to login page to get authenticated). This is explained also in pinia documentation on use of pinia outside of components. But I can't get my head around it.
The store I use is currently simple (return false or true on isAuthenticated):
//authStore.js
import { defineStore } from "pinia";
export const useAuthStore = defineStore( 'AuthStore', {
state: () => {
return {
isAuthenticated: false
}
}
})
I want to use this isAuthenticated in a beforeEnter in routes/index.js
In main.js:
import { useAuthStore } from '#/stores/authStore'
import { createApp } from 'vue'
import App from './App.vue'
import router from '#/router'
import { createPinia } from 'pinia'
const app = createApp(App)
app.use(createPinia()).use(router)
app.mount('#app')
// I'm not using authStore in this file, so this raises an error:
const authStore = useAuthStore()
And in router/index.js:
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
// path for login page,
{
path: '/adm/home',
name: 'AdmView',
component: () => import('#/views/adm/AdmView.vue'),
beforeEnter: (to) => {
// error: authStore is not defined
const isAuthenticated = authStore
if ( !isAuthenticated && to.name !== 'login-adm' ) {
return { name: 'login-adm' }
}
}
},
// other paths
]
Your authStore.js exports useAuthStore as expected, but you do not call it as required.
authStore is not defined because authStore is the filename of your auth store -- instead you should be executing the function useAuthStore exported from that file:
const authStore = useAuthStore();
console.log('Is authenticated?', authStore.isAuthenticated);
I don't know if it is the main issue but u forgot to use a destructor on userStore doing
const isAuthenticated = authStore
It supposed to be
const { isAuthenticated } = toRefs(authStore);
toRefs to preserve reactivity after passing. It can be imported as
import { toRefs } from 'vue';
I can't access my routes from the store.
There may be a good explanation for this.
I use Vuejs3 and Pinia
My store :
import {defineStore} from 'pinia'
import {useRoute} from "vue-router";
type navigationState = {
selectedNavigationItem: INavigationItem | null,
selectedNavigationPage: INavigationPage | null,
}
export const useNavigationStore = defineStore('navigationStore', {
state: () => ({
/**
* when the user clicks on an element of the navbar we store the navigation item here
*/
selectedNavigationItem: null,
/**
* when the user clicks on an element of the sidebar we store the navigation page here
*/
selectedNavigationPage: null,
} as navigationState),
actions: {
/**
* Set Selected navigation page
* #param navigationPage
* #type INavigationPage
*/
setSelectedNavigationPage(navigationPage: INavigationPage | null) {
console.log(useRoute())
this.selectedNavigationPage = navigationPage
},
},
})
when I do a console log like in the method setSelectedNavigationPage
I have an undefined
useRoute and useRouter must be used in Vue components and specifically setup method or inside script setup.
useRouter Docs
useRoute Docs
If you want to access the router though, you can simply import it:
router-file
import { createRouter, createWebHistory } from 'vue-router'
export const router = createRouter({
history: createWebHistory(),
routes: [/* ... */]
})
then in your pinia store you can import and use the router from that file:
import { defineStore } from 'pinia'
import router from './router'
export const myStore = defineStore('myStore', () => {
// router.push
// router.replace
})
EDIT: Thanks for sophiews for pointing this out.
Just found out that we have different way to defineStore: Setup Stores
// src/stores/user.js
import { defineStore } from 'pinia'
import { useRoute, useRouter } from 'vue-router'
import api from './api.js'
export const useUserStore = defineStore('User', () => { // use function
const route = useRoute()
const router = useRouter()
const login = async () => {
await api.POST('login', {username, password})
router.replace({name: 'home'})
}
return { login } // IMPORTANT: need to return anything we need to expose
})
Old answer
You can add router as Pinia plugin
// src/main.js
import { createPinia } from 'pinia'
import { createApp, markRaw } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
import Home from './views/HomePage.vue'
import Api from './api.js' // my axios wrapper
const app = createApp(App)
// I usually put this in a separate file src/router.js and export the router
const routes = [
{ path: '/', component: HomePage },
]
const router = createRouter({
history: createWebHistory(),
routes,
})
const pinia = createPinia()
pinia.use(({ store }) => {
store.router = markRaw(router)
store.api = markRaw(Api)
})
app
.use(pinia)
.use(router)
.mount('#app')
Then router and api are available on this
// src/stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('User', {
state: () => ({}),
actions: {
async login() {
await this.api.POST('login', {username, password})
this.router.replace({name: 'home'})
}
}
})
Note that you can't call this.router with arrow function.
login: async () => {
this.router.replace({name: 'home'}) // error
}
For typescript user, to correctly get type for this.router and this.api:
// src/global.d.ts
import { Router } from 'vue-router'
import Api from './api'
export { }
declare global {
}
declare module 'pinia' {
export interface PiniaCustomProperties {
router: Router,
api: typeof Api
}
}
I found this way on pinia github.
https://github.com/vuejs/pinia/discussions/1092
But I still don't know how to add this.route to Pinia.
Future reader, please comment if you know how to do it.
You could wrap the process of instantiating a store within a factory/function, this will allow you to expand the stores capabilities regarding your custom needs. Below you can see that we can instantiate a store referencing the urql client and the router object.
Have a look:
export class StoreManager {
static _instances: any[] = [];
public static spawnInstance(
id: string,
storeType?: EStoreType,
clientHandle?: ClientHandle,
routerHandle?: Router,
) {
if (StoreManager._instances.find((i) => i.id === id)) {
const store = StoreManager._instances.find((i) => i.id === id).instance;
return store;
} else {
const store = StoreManager.initStore(
id,
storeType,
clientHandle ?? null,
routerHandle ?? null,
);
StoreManager._instances.push({
id: id,
instance: store,
storeType: storeType,
});
return store;
}
}
public static initStore(
id: string,
storeType: EStoreType,
clientHandle: ClientHandle | null,
routerHandle: Router | null,
) {
const baseState = {
_meta: {
storeType: storeType,
isLoading: true,
},
_client: clientHandle,
_router: routerHandle,
};
const baseActions = {
async query(query: any, variables: any[] = []) {
// use urql client
},
};
const baseGetters = {
storeType: (state) => state._meta.storeType,
getCurrentRoute: (state) => {
if (!state._router) {
throw new RouterNotSetException(
`This store does not have a router set up`,
);
}
return state._router.currentRoute.fullPath.replace('/', '');
},
};
switch (storeType) {
case EStoreType.DEFAULT:
return defineStore({
id: `${id}`,
state: () => ({
...baseState,
}),
actions: {
...baseActions,
},
getters: {
...baseGetters,
},
});
default:
throw new StoreTypeNotFoundException(
`Expected valid 'EStoreType', got ${storeType}`,
);
}
}
}
Within your VueComponent a store instance would be spawned like this:
const store = StoreManager.spawnInstance(
uuidv4(),
EStoreType.DEFAULT,
useClientHandle(),
useRouter(),
)();
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>
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;
},
},
});