I'm aware of the watchQuery parameter but it only works for the query parameters. Consider the following case:
/report
and /report/10
resolves to the same page component and when the user is in the /report
page and saves the report, we redirect the user to the /report/10
but prevent Nuxt to reload the page component as we have all the data and the state is updated.
I tried to use watchQuery
but it didn't work so I had to try native history.pushState
but it seems to cause trouble in navigation. (user triggered back/forward buttons).
If /report
and /report/10
resolves to the same route, when I call $route.push
, I should be able to reuse the component so the page reload should not be triggered when an optional flag is enabled.
Duplicate of https://github.com/nuxt/nuxt.js/issues/4663
@manniL Unfortunately, the previous issue doesn't provide any reliable solution for this feature.
The first problem with the key
approach is that on the latest version of Nuxt, it calls data()
so cause the state to be reset even though we have key
attribute which is a static value.
Also setting the value of key
automatically disable the navigation and every $route.push
that resolves to the same component ends up being impotent. It would be much better to have an API similar to watchQuery
, the purpose of key
seems to be different than this use-case.
I would vote for the issue to be re-opened.
Makes sense. 馃憤
I realized that even though I redirect the user to another route, the page redirect doesn't work when key
is defined.
Hi @buremba
In this particular scenario, you want to tell Nuxt to not load any asyncData
from the matched page when you call this.$router.push('/report/10')
right?
Hey @Atinux, correct but the issue seems to be more complex than the asyncData
issue. Nuxt doesn't really load pages even though the path resolves to another route.
Hi @buremba
Can you look at my issue #5257, Is it related to this discussion?
@webspilka I don't think so. This one is a feature request.
Side note: this would also somewhat help with #4132 because the transition (presumably) wouldn't trigger.
@johnRivs that seems to be the case. I guess that this issue has many side-effects.
Any updates on this issue?
I'm also awaiting updates on this issue, as I'm suffering the consequences of #4132 - API content appears -> transition occurs -> API content re-appears. In my case I can't use beforeMount()
as suggested in that issue.
An FYI, it's a hack, but I was able to work-around Nuxt data()
refreshing my component data on route change by copying forward the data from the previous life-cycle as folows
export default {
key: '_bar',
data () {
return {
myVariable: this.myVariable || 'my default value'
}
}
}
We also use these tricks as a workaround but it caused other issues in our app. Is there anyone working on this issue? If not, the core maintainers let me know where to look, I can try to create a PR maybe?
Hi @buremba
You need to look into packages/vue-app/packages/vue-app/template/client.js
But his behavior is tricky since it bypasse middleware/validate. What about using the store to keep the last page data and avoid fetching again if the same data?
@Atinux yes, we again found a workaround but it's fragile and causes more issues as we upgrade Nuxt. :( I will look at it when I have time and try to send a PR, thanks!
Any proper workaround for this?
I have a route like this /items/:id
. All the items are fetched from api using asyncData
, and displayed on the page. A tiny list of items is displayed to the user and when user clicks an item (which is a nuxt-link) the url changes, I watch the param $route.params.id
and scroll the content to show the item existing on the page. However, I noticed 2 things that nuxt does which I don't want it to do:
asyncData
hookFor the issue 1, setting key:
to a static value was the exact solution. However, for the issue 2 nuxt has nothing to offer, so I had to patch the module using patch-package
to add an option to ignore route param change detection. Here it is, if it would help someone.
@nuxt+vue-app+2.13.3.patch
diff --git a/node_modules/@nuxt/vue-app/template/client.js b/node_modules/@nuxt/vue-app/template/client.js
index 1d3ba1f..e416fef 100644
--- a/node_modules/@nuxt/vue-app/template/client.js
+++ b/node_modules/@nuxt/vue-app/template/client.js
@@ -150,9 +150,31 @@ function mapTransitions (toComponents, to, from) {
}
<% } %>
async function loadAsyncComponents (to, from, next) {
+ const loadComponents = () => resolveRouteComponents(to, (Component, instance) => ({ Component, instance }))
+
+
// Check if route changed (this._routeChanged), only if the page is not an error (for validate())
this._routeChanged = Boolean(app.nuxt.err) || from.name !== to.name
this._paramChanged = !this._routeChanged && from.path !== to.path
+ if (this._paramChanged) {
+ const Components = await loadComponents()
+ const changedParams = []
+
+ Object.keys({ ...from.params, ...to.params }).forEach((key) => {
+ if (from.params[key] != to.params[key]) {
+ changedParams.push(key)
+ }
+ })
+
+ this._paramChanged = !Components.some(({ Component, instance }) => {
+ const ignoreParams = Component.options.ignoreParams
+
+ if (ignoreParams && !changedParams.filter(x => !ignoreParams.includes(x)).length) {
+ return true
+ }
+ })
+ }
+
this._queryChanged = !this._paramChanged && from.fullPath !== to.fullPath
this._diffQuery = (this._queryChanged ? getQueryDiff(to.query, from.query) : [])
@@ -164,10 +186,8 @@ async function loadAsyncComponents (to, from, next) {
try {
if (this._queryChanged) {
- const Components = await resolveRouteComponents(
- to,
- (Component, instance) => ({ Component, instance })
- )
+ const Components = await loadComponents()
+
// Add a marker on each component that it needs to refresh or not
const startLoader = Components.some(({ Component, instance }) => {
const watchQuery = Component.options.watchQuery
@nuxt+types+0.7.9.patch
diff --git a/node_modules/@nuxt/types/app/vue.d.ts b/node_modules/@nuxt/types/app/vue.d.ts
index a58897e..dd89a55 100644
--- a/node_modules/@nuxt/types/app/vue.d.ts
+++ b/node_modules/@nuxt/types/app/vue.d.ts
@@ -18,6 +18,7 @@ declare module 'vue/types/options' {
layout?: string | ((ctx: Context) => string)
loading?: boolean
middleware?: Middleware | Middleware[]
+ ignoreParams?: string[]
scrollToTop?: boolean
transition?: string | Transition | ((to: Route, from: Route) => string)
validate?(ctx: Context): Promise<boolean> | boolean
Create a mixing that fixes both issues:
export function ignoreParamsMixin(...params: string[]) {
return Vue.extend({
/// Returns a route key with `params` excluded
key: (route) => {
const pathParts = route.matched[0].path.split('/')
const actualParts = route.path.split('/')
const keyParts = actualParts.map((x, i) => {
const part = pathParts[i]
if (part?.startsWith(':')) {
const param = part.replace(/(^:)|(\?$)/g, '')
if (params.includes(param)) {
return ''
}
}
return x
})
return keyParts.filter(x => x).join('-')
},
/// Used in the above patch to make nuxt not call `asyncData` if the specified params changed.
ignoreParams: params,
})
}
Usage:
/// my page component for `items/:id` route
export default Vue.extend({
layout: '...',
/// Make nuxt not remount the page component and not call asyncData hook if `id` param is changed
mixins: [ignoreParamsMixin('id')],
watch: {
'$route.params.id'() {
/// scroll to the item
},
},
async asyncData() {...
})
We are using the route params as filters in our search page. So it makes sense for us to treat a param change (e.g., a category slug) and a query change (e.g., filter by featured) exactly the same way. Our URLs look like:
https://www.example.com/jobs/<category_slug>/<place_slug>?contract_type=<contract_type>&featured=<featured>
Luckily, we found the solution in the watchParam
option :tada: that can be found in a few lines of the client.js
file pointed out by @Atinux.
If watchParam: false
then the data()
isn't called, keeping the component state through consecutive URL changes. In other words, it works like the watchQuery
, though you need a watcher on the $route
to track the parameter changes (as probably you're already doing).
This is how we are using it with nuxt-property-decorator
(uses vue-class-component
):
import { Component, Vue, Watch } from 'nuxt-property-decorator';
@Component({
key: 'JobsPage',
watchParam: false
})
export default class JobsPage extends Vue {
/**
* Called on the first load, route params change, or query params change
* NOTE: If you need to render initial data on SSR, then remove the `immediate` and use `fetch()` for the first load.
*/
@Watch('$route', { immediate: true })
async routeChanged(to: Route, from: Route) {
// Check the route still matches the JobsPage component
if (this.$route.name == 'jobs') {
// Update filters based on the URL, and perform a new search.
}
}
}
As it isn't in the types declaration, we had to extend the interface. This is the content of our types/vue.d.ts
:
import Vue from 'vue';
declare module '*.vue' {
export default Vue;
}
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
watchParam?: boolean;
}
}
@manniL is there any reason why this parameter isn't documented? Shouldn't it be included in the types declaration? Thanks!
@buremba does it work for your use case?
Here is the PR where this feature was introduced: https://github.com/nuxt/nuxt.js/pull/6244
@emarbo we had to switch query parameters in order to make it work but it looks like a legit solution!
@manniL is there any reason why this parameter isn't documented? Shouldn't it be included in the types declaration? Thanks!
Must've been missed out. PR to the docs welcome!
Closing here as #6244 covers the issue.
Most helpful comment
Makes sense. 馃憤