Navigate with Vue router-link changing only query parameters, not path - vue.js

In Vue.js, I'm using <router-link> to navigate, as such:
<router-link :to="{ path: '/', query: { q: item.id, lang: lang } }">{{item.name}}</router-link>
This does not update the path, only the query string. Although the resulting URL is formatted correctly, it does not trigger navigation in Vue, apparently because the path has not changed, only the query string.
If I put a beforeRouteUpdate hook on my component, I can see that the new query parameters appear in the "to" object.
How can I make Vue perform the navigation, even though only the query parameters have changed?

You have most probably solved this already, so this answer could be useful for others with this problem.
Documentation link - In-Component Guards - beforeRouteUpdate
If I put a beforeRouteUpdate hook on my component, I can see that the new query parameters appear in the "to" object
This means you doing it all correctly since you are seeing the new parameters in the component guard, So next, what you need to do is to do as prescribed below in docs and example
Fetching After Navigation
So all you now have to do is fetch your new data using these new parameters
beforeRouteUpdate (to, from, next) {
this.post = null
// replace `getItem` with your data fetching util / API wrapper
getItem(to.params.q, to.params.lang, (err, post) => {
this.post = post
next()
})
}

Related

nuxt route change query endless loop

I have a nuxt one-pager website and I am trying to set up a router for the language switcher. My headless CMS (Storyblok) has language logic setup by using ?language=xyz.
The data is fetched by using AsyncData() hook in the pages/index.vue.
Now when I change the route from '/' to '/xyz' the AsyncData() is called again and the route changes.
When I do not change the path but only the query (language=xyz) this does not happen.
I tried to add the following:
beforeRouteUpdate (to, from, next) {
console.log('BEFORE UPDATE', to.query.language);
console.log('BEFORE UPDATE 2', this.$route.query.language);
if(to.query.language != this.$route.query.language) {
//this.$router.push({ path: '/', query: { language: to.query.language } })
next();
}
}
When I now click on the navigation I can see in the console log, the current and new language but still nothing happens.
When I try to push the route (commented out code above) I get an endless loop and see the beforeRouteUpdate running again and again, but the AsyncData is not run.
What can I do, so the behavior is the same as when the path of the route changes?
Are you sure that you are actually fetching it again from storyblok? Are you fetching it with the storyblok module or are you fetching it straight from the API manually?
What I would suggest as a counter option would be to add the nuxt i18n module and fetch the data from storyblok with the i18n language and use the i18n language switch feature! That way you can also add translations that don't fit into the storyblok structure.

Pagination is not working as expected with vue router

I am pushing query parameter for page change it works fine:
getProducts(){
this.$router
.push({
name: 'products',
query: {
page: this.page,
},
})
.catch(() => {})
... fetching data from backend
}
It works fine when I just click pagination items and data is loading correctly but when I click back from browser query param is geting changed but pagination and data doesn't can't changed because this.page value remains the same. How can be this fixed?
You will have to add a befoureRouteUpdate(to, from, next) hook in your component - or a watcher on $route. Vue-Router reuses the same component because you are not changing the route - only the query parameters.
Keep in mind that:
the beforeRouteUpdate will be called only when the component is being reused - not when the route is visited for the first time (for the latter you need beforeRouteEnter)
the watcher will be also called when you leave the route (e.g. if you go to clients page)

Difference between beforeRouteUpdate and watching '$route' - Vue.js?

As we know, to react to params changes in the same component we use beforeRouteUpdate hook or watching $route.
watching $route:
const User = {
template: '...',
watch: {
'$route' (to, from) {
// react to route changes...
}
}
}
beforeRouteUpdate Method:
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
next()
}
}
What is the difference between these two? if both are same then why vue router introduced beforeRouteUpdate?
From the documentation on beforeRouteUpdate:
called when the route that renders this component has changed, but this component is reused in the new route. For example, given a route with params /users/:id, when we navigate between /users/1 and /users/2, the same UserDetails component instance will be reused, and this hook will be called when that happens. Because the component is mounted while this happens, the navigation guard has access to this component instance.
The documentation is admittedly unclear that the hook gets called before the value of the $route object actually changes. That's the difference between this navigation hook and setting a watcher on $route, which will get called after the value of $route has changed.
Using the beforeRouteUpdate navigation guard, you can determine whether or not you want to prevent the route from changing (by not calling next()) or go to a different route entirely (by passing a different route value like next('/foo'), next({ name: 'foo' }), etc.).
Here's an example fiddle that shows when these functions get called.
As #thanksd said $route is like guard. With watching you can't prevent the route from taking action, but with beforeRouteUpdate you can do it with next function. for example you can wait untill your data is fetched then proceed to the component.
You can find more information in vue-router documentation Data Fetching.

Vue-Router won't work when a route is activated

Consider about routes below:
/form/1
/form/2
/form/3
When the address is / and I click on /form/1 VueRouter loads my component inside router-view but when I'm in /form/1 by clicking on /form/2 nothing happens!
You need to watch for param changes in the $route and perform the correct action (e.g. making an ajax call to retrieve the information for the given id), so:
watch: {
'$route' (to, from) {
// React to route change
}
}
You may also use the beforeRouteUpdate guard (see: https://router.vuejs.org/en/essentials/dynamic-matching.html)
You can also add a unique key attribute to the router-view so that vue forcefully replaces the component instead of reusing it
<router-view :key="$route.path"></router-view>

How can I dynamically set destination component in Vue router

I am trying to dynamically set the target component for a vue-router route.
The reason is that I need to retrieve data from the server to know the correct component to target.
One solution I thought about was to add a global guard similar to:
router.beforeEach((to, from, next) => {
get_data_from_server(to.fullPath).then(() => {
// set the "to" component, but how?
next();
})
But, as the comment says, how would I set "component" - there is no component property in the "to" object. (It looks like it is in components.default.render but that isn't documented and doesn't look like it should be messed with.)
Another alternative is to the use above logic but add special-case code to redirect but then I found there was no way to create a dynamic/new route that targeted the correct component.
How should one go about creating a route to a destination that is not known at build-time?
This solution seems to work.
router.beforeEach((to, from, next) => {
get_data_from_server(to.fullPath).then(component => {
if (to.matched.length === 0) {
// the target route doesn't exist yet
router.addRoutes({path: to.path, component: component})
next(to.fullPath)
return
} else {
// the route and component are known and data is available
next()
}
})
The key was finding the link in my comment above that shows the vue-router commit that implements addRoutes().
One thing to be aware of is that new routes are added at the end (makes sense) so any wildcards present might create unintended matches. I had a catchall route in place that redirected to a default page. But that matched before the newly added match because it was earlier the list. And that caused a stack overflow. In effect the code after if (to.matched.length === 0) is the default route.