Skip to content

Is there any way to force navigation when clicking on a router-link even if the route doesn't change? #2430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
trandaison opened this issue Oct 13, 2018 · 19 comments

Comments

@trandaison
Copy link

trandaison commented Oct 13, 2018

What problem does this feature solve?

Reload current page's state or refresh the page.

For example, I have a <HeaderBar> which contains a Home button, just like the Facebook one.
If I'm in another page (rather than Home), when I hit this button, it will navigate to Home. But when I'm in Home page, hit this button it does nothing.

It's expected to reload the page, and scroll to the top, right?

Please don't tell me to write out an action to refresh the state and scroll to top. This header bar is a shared component, and the Home button can be hit at any where.

What does the proposed API look like?

Some thing like

<router-link :to="{ name: 'Home', force: true }">Home</router-link>

or

this.$router.replace({ name: 'Home', force: true });
@posva
Copy link
Member

posva commented Oct 13, 2018

You can reload the page, but you should handle the refresh in a click handler if you want to have that custom behaviour

Duplicate of #974

@posva posva closed this as completed Oct 13, 2018
@trandaison
Copy link
Author

@posva
If so, Navigation Guards (beforeRouteLeave, beforeRouteUpdate) will not be triggered.

How do you deal with that?

@posva
Copy link
Member

posva commented Oct 17, 2018

yeah, they are not supposed to be triggered as you are in the same route with no param change or whatsoever... You need to refactor the code in methods and call them in both places. Cheers

@trandaison
Copy link
Author

[Solved]
This is my solution, if any body needs.

I create a base component, and use it instead of <router-link>.
If you hit the link when you're in that page already, it will create a query name _, so the fullPath will be change and the page will be "reload".

You can change the query name to whatever you want, and use the timestamp or some unique string to make the fullPath changes.

<template lang="html">
  <router-link v-bind="$attrs" :to="to" @click.native="clickHandler">
    <slot></slot>
  </router-link>
</template>

<script>
export default {
  name: 'BaseLink',

  props: {
    to: {
      type: [Object, String],
      default: '/',
    },
  },

  data() {
    return {
      currentRoute: null,
    };
  },

  created() {
    this.currentRoute = this.$route.name;
  },

  methods: {
    clickHandler($event) {
      if (!($event.ctrlKey || $event.metaKey) && this.currentRoute === this.to.name) {
        const query = { ...this.$route.query, _: new Date().getTime() };
        const newRoute = { ...this.$route, query };
        this.$router.replace(newRoute);
      }
    },
  },
};
</script>

@leoyli
Copy link

leoyli commented Oct 27, 2018

I doubt this method, it looks dirty and contaminate the history (and generate no real-meaning query in the url). To be clear, you don't want a "real, F5 like" refresh since your script have to be reload and the browser have to reparse it, right? (The OP's solution is in that direction.)

More simple way is to assign an unique key in <router-view /> using one of the properties stored in the params. Manipulate this instead of query would be better.

@trandaison
Copy link
Author

@leoyli

Yeah, I've tried to bind a key to router-view before, but it didn't work.

<router-view :key="$route.fullPath"/>

Since the fullPath have no changes, how does it suppose to reload the page?

Can you give me an example, plz. Talk is cheap, you know.

@leoyli
Copy link

leoyli commented Oct 28, 2018

You can have something like

<router-view :key="$route.params.theNameYouGive"/>

And you manipulate like:

<router-link :to="{ params: { theNameYouGive: Data.now } }" replace> ... </router-link>

Remember to is passed router.push() and it accept an object also. Doing that, it is more declarative and controllable. I'm using this to decide if the page of component should be rerendered since they will based on id params obtained from URL entry, and my child component can still using nesting <router-view>.

@trandaison
Copy link
Author

trandaison commented Nov 2, 2018

@leoyli
Did you try it in actually?
It doesn't work. The theNameYouGive doesn't make the $route change, beforeRouteUpdate, beforeRouteLeave doesn't triggered and it never refreshes the page.

@leoyli
Copy link

leoyli commented Nov 3, 2018

@trandaison
Sorry my bad. Looks like <router-link> can only change the params when it assigned together with name and this name can not be the same as the current route; or in your route definition you have :param bound with the url.

That is to say in any cases URL have to be change unless you assign key to an internal state (data) and you bind the property name in the key attribute, e.g. <router-view :key="counter">, and use @click="counter++" to update the value in the component.

If you still want to use the approach by changing URL, I suggest you manipulate hash instead of query.

@masquevil
Copy link

Try: npm i vue-state-router instead of vue-router. Totally same API.
Gives you these feature:

  1. when navigate with push mode, every component will be reload, and scroll back to top
  2. when with replace mode, every component will reuse, and scroll to saved position

@n10000k
Copy link

n10000k commented May 4, 2019

I've just ran into this problem also.

Use case:

  • Router setup as /profile/:userId to a component called "Profile"
  • On the profile it loads the data from a api call, however let's say I have a "Friends" tab on the page and I click their profile :to profile with the userId params
  • It won't load because the component is already loaded

@cafe4it
Copy link

cafe4it commented May 7, 2019

Same problem.
Resolved : click link profile event:

() => window.location.href= `/profile/${userId}`

@pedzed
Copy link

pedzed commented Nov 17, 2019

@posva is there a technical limit? I do not see how this would not be the desired default effect.

@trandaison
Copy link
Author

@cafe4it but doing so your app is not SPA anymore, the whole app will be reload again.

@trandaison
Copy link
Author

trandaison commented Nov 20, 2019

This is my trick to get through this situation:

  • Create a component named <base-link> just likes the <router-link> and use this one instead of <router-link>.
  • If the :to route is different from the current route, render <router-link> in the template.
  • Else, render a <a> tag with :href=this.$router.resolve(this.to).route.fullPath and add an click event handler to redirect to another page named 'Refresh';
  • In the Refresh page, replace the route back to the referer.

Everything works just fine so far.

@Dima000
Copy link

Dima000 commented Aug 24, 2020

I have a nice trick. It works with nuxt-link from Nuxt.js, but it can also work with router-kink I believe.

First, you register a click event on your link component
<nuxt-link to="/home" @click.native="onClick" />

After that, you check in the click handler if you are on the same route

if(this.$route.path === '/home') {
    // do some staff here
   // example
   window.scrollTo({ top: 0, behavior: 'smooth' })
}

The condition will be true only when you click the navigation link for the same route

@CMCDragonkai
Copy link

I'm interested in having some sort of functionality here that does this natively.
I want the vue-router to think of the URL like a form-input. So I can render text into it without triggering changes that depend on the URL. At the sametime the URL is a control input.

@n10000k
Copy link

n10000k commented Jan 4, 2021

@posva Can this be re-opened? There seems to be a lot of community support for a option to force a re-render. Please see the use case I shared above, I would love the option to force without the additional complexity of adding a watcher to accomplish something so simple. Thanks in advance.

@grafikri
Copy link

grafikri commented Jan 5, 2021

I added a query param that includes current time as milliseconds and it's working for me.

this.$router.push({ name: 'post', params: { type: 'detail' }, query: { id: 123, time: Date.now() } });

@vuejs vuejs locked as spam and limited conversation to collaborators Jan 5, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants