I am trying to switch over to vuejs3 and the new vue-router.
Now I see that beforeRouteEnter is not exposed:
// same as beforeRouteUpdate option with no access to `this`
onBeforeRouteUpdate((to, from, next) => {
// your logic
console.log("Hello world") //this is only triggered if the id changes
next()
})
So my question is: How can I trigger the initial axios-requests on a specific route like /dashboard as I did before?
It's not possible to execute code in setup before the route enters because at the time of setup the navigation has already been confirmed.
Another option #1
You can still use the options api, so you could still use beforeRouteEnter:
setup() {
...
},
beforeRouteEnter(to, from, next) {
console.log(to);
}
Another option #2
Use beforeEnter in the router:
{
path: '/foo',
component: Foo,
beforeEnter(to) {
console.log(to)
}
}
Related
I have a Nuxt3 project where I'd like to add new routes based on an API call to a database. For example, let's say a user navigates to /my-product-1. A route middleware will look into the database and if it finds an entry, it will return that a product page should be rendered (instead of a category page, for example).
This is what I came up with:
export default defineNuxtPlugin(() => {
const router = useRouter()
addRouteMiddleware('routing', async (to) => {
if (to.path == '/my-awesome-product') {
router.addRoute({
component: () => import('/pages/product.vue'),
name: to.path,
path: to.path
})
console.log(router.hasRoute(to.path)) // returns TRUE
}
}, { global: true })
})
To keep it simple, I excluded the API call from this example. The solution above works, but not on initial load of the route. The route is indeed added to the Vue Router (even on the first visit), however, when I go directly to that route, it shows a 404 and only if I don't reload the page on the client does it show the correct page when navigated to it for the second time.
I guess it has something to do with the router not being updated... I found the following example in a GitHub issue, however, I can't get it to work in Nuxt3 as (as far as I'm aware) it doesn't provide the next() method.
When I tried adding router.replace(to.path) below the router.addRoute line, I ended up in an infinite redirect loop.
// from https://github.com/vuejs/vue-router/issues/3660
// You need to trigger a redirect to resolve again so it includes the newly added
route:
let hasAdded = false;
router.beforeEach((to, from, next) => {
if (!hasAdded && to.path === "/route3") {
router.addRoute(
{
path: "/route3",
name: "route3",
component: () => import("#/views/Route3.vue")
}
);
hasAdded = true;
next('/route3');
return;
}
next();
});
How could I fix this issue, please?
Edit:
Based on a suggestion, I tried using navigateTo() as a replacement for the next() method from Vue Router. This, however, also doesn't work on the first navigation to the route.
let dynamicPages: { path: string, type: string }[] = []
export default defineNuxtRouteMiddleware((to, _from) => {
const router = useRouter()
router.addRoute({
path: to.path,
name: to.path,
component: () => import ('/pages/[[dynamic]]/product.vue')
})
if (!dynamicPages.some(route => route.path === to.path)) {
dynamicPages.push({
path: to.path,
type: 'product'
})
return navigateTo(to.fullPath)
}
})
I also came up with this code (which works like I wanted), however, I don't know whether it is the best solution.
export default defineNuxtPlugin(() => {
const router = useRouter()
let routes = []
router.beforeEach(async (to, _from, next) => {
const pageType = await getPageType(to.path) // api call
if (isDynamicPage(pageType)) {
router.addRoute({
path: to.path,
name: to.path,
component: () => import(`/pages/[[dynamic]]/product.vue`),
})
if (!routes.some(route => route.path === to.path)) {
routes.push({
path: to.path,
type: pageType,
})
next(to.fullPath)
return
}
}
next()
})
})
I suggest you use dynamic routing within /page directory structure - https://nuxt.com/docs/guide/directory-structure/pages#dynamic-routes
The [slug] concept is designed exactly for your usecase. You don't need to know all possible routes in advance. You just provide a placeholder and Nuxt will take care of resolving during runtime.
If you insist on resolving method called before each route change, the Nuxt's replacement for next() method you're looking for is navigateTo
https://nuxt.com/docs/api/utils/navigate-to
And I advise you to use route middleware and put your logic into /middleware/routeGuard.global.ts. It will be auto-executed upon every route resolving event. The file will contain:
export default defineNuxtRouteMiddleware((to, from) => {
// your route-resolving logic you wanna perform
if ( /* navigation should happen */ {
return navigateTo( /* your dynamic route */ )
}
// otherwise do nothing - code will flow and given to.path route will be resolved
})
EDIT: However, this would still need content inside /pages directory or some routes created via Vue Router. Because otherwise navigateTo will fail, as there would be no route to go.
Here is an example of one possible approach:
https://stackblitz.com/edit/github-8wz4sj
Based on pageType returned from API Nuxt route guard can dynamically re-route the original URL to a specific slug page.
I have a beforeEach route guard that calls fetchWorkspaces action. This action performs a request to Axios and its response populates the state.
However, when the state is called from the created hook in the component and I refresh the page I do not get the values in the console, but the observer instead.
created() {
console.log(this.workspace) # returns {__ob__: Observer}
}
The action is returning a promise but the created hook is not waiting for that promise to resolve.
This is the hook from the router:
router.beforeEach((to, from, next) => {
store.dispatch('fetchWorkspaces').then(() => {
next()
})
}
And this is the action and its mutation:
export default {
state: {
workspaces: []
},
mutations: {
SET_WORKSPACES(state, workspaces) {
state.workspaces = workspaces.workspaces
}
},
actions: {
fetchWorkspaces({ commit }) {
return Vue.axios.get('/workspaces').then(response => {
commit('SET_WORKSPACES', response.data)
})
}
}
The created is called after the beforeEach hook. I do not understand why this behavior is happening and how to fix it.
The reason I want to get access the newly state data from created is to call other actions that will fetch resources based on this state data.
See this and that. I think the Vue is created first before your router register beforeEach. So, you should use beforeEach before you initiate Vue instance.
Why does the beforeRouteEnter navigation guard exist in vue-router? Are there instances where beforeRouteEnter will be fired, but mounted will not be? If not, in what instance would you prefer using beforeRouteEnter to mounted?
The mounted is a lifecycle hook of any Vue component, it'll always be triggered. The idea of beforeRouteEnter or any other lifecycle hook added by the vue-router is to allow you to control your application.
For example, let's say that you have a route called bar which has a really specific validation logic that only allow the user to enter in it if the previous route was foo, you may insert that validation logic inside this hook instead of checking every route change in the global guard.
export default {
name: 'Bar',
beforeRouteEnter(to, from, next) {
if (from.name === 'foo') {
next(); // Calling next allow the route to proceed
} else {
next(false); // Don't allow the navigation
// or
next({
name: 'foo',
query: {
from: 'bar'
}
}); // Redirect to any desired route as navigation made in $router
}
}
}
I am trying to create a global "unsaved changes confirm" functionality.
So whenever some form is edited, I set a global vuex state saying there's unsaved changed, and then I want the user to confirm before the route changes.
I have created a global mixin - and it works fine, except when theres nested router views - then the beforeRouteLeave is fired twice, and then the next() function doesn't work?
This is how it looks:
Vue.mixin({
computed: {
unsavedChanges() {
return this.$store.getters["helpers/unsavedChanges"]
}
},
beforeRouteLeave (to, from, next) {
if (this.unsavedChanges) {
this.$Modal.confirm({
title: 'Unsaved changes',
content: '<p>Are you sure you wish to leave the page?</p>',
okText: 'Continue',
cancelText: 'Cancel',
onOk: () => {
next()
},
onCancel: () => {
next(false)
}
});
}
}
})
It works fine whenever there's no sub router views, but if there are, then the router doesnt change, even if you click Continue and the next() function is called.
How do I solve this? Thanks.
I want to finish some information verify before one component leave.
I've scanned the vue-router document: https://router.vuejs.org
But I use vue-cli, in my file: router1.vue,
console.log(this.$router.beforeLeave) -> undefined
How can I use it?
Add this to your router1.vue:
export default {
//...
beforeRouteLeave (to, from, next) {
// called when the route that renders this component is about to
// be navigated away from.
// has access to `this` component instance.
},
//...
}
for example:
beforeRouteLeave (to, from , next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
And it will be called before this route leave.
ref: https://router.vuejs.org/en/advanced/navigation-guards.html