ChatGPT解决这个技术问题 Extra ChatGPT

NavigationDuplicated Navigating to current location ("/search") is not allowed

When I want to do a search multiple times it shows me the NavigationDuplicated error. My search is in the navbar and the way I have configured the search is to take the value using a model and then pass the value as a parameter to the ContentSearched component, and then receive the value of the search in that component.

I know the right way is to use an emitter, but I still don't know how to learn to use it. To access the emit is context.emit('', someValue)

NavigationDuplicated {_name: "NavigationDuplicated", name: "NavigationDuplicated", message: "Navigating to current location ("/search") is not allowed", stack: "Error↵    at new NavigationDuplicated (webpack-int…node_modules/vue/dist/vue.runtime.esm.js:1853:26)"}

NavBar.vue

<template>
  <nav class="navbar navbar-expand-lg navbar-dark bg-nav" v-bind:class="{'navbarOpen': show }">
    <div class="container">
      <router-link to="/" class="navbar-brand">
        <img src="../assets/logo.png" alt="Horizon Anime" id="logo">
      </router-link>

      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" v-on:click.prevent="toggleNavbar">
        <span class="navbar-toggler-icon"></span>
      </button>

      <div class="collapse navbar-collapse" id="navbarSupportedContent" v-bind:class="{'show': show }">
        <ul class="navbar-nav mr-auto">
          <li class="nav-item">
            <router-link class="nav-link" to="/" ><i class="fas fa-compass"></i> Series</router-link>
          </li>
          <li class="nav-item">
            <router-link class="nav-link" :to="{name: 'EpisodesSection'}" ><i class="fas fa-compact-disc"></i> Episodios</router-link>
          </li>
          <li class="nav-item">
            <router-link class="nav-link" :to="{name: 'MovieSection'}" ><i class="fas fa-film"></i> Peliculas</router-link>
          </li>
        </ul>
        <div class="search-bar">
          <form class="form-inline my-2 my-lg-0">
            <input class="form-control mr-sm-2" v-model="query" type="search" placeholder="Buscar películas, series ..." aria-label="Search">
            <button class="btn btn-main my-2 my-sm-0" @click.prevent="goto()" type="submit"><i class="fas fa-search"></i></button>
          </form>
        </div>
      </div>
    </div>
  </nav>
</template>

<script>
  import {value} from 'vue-function-api';
  import {useRouter} from '@u3u/vue-hooks';

  export default {
    name: "NavBar",
    setup(context){
      const {router} = useRouter();
      const query = value("");

      let show = value(true);
      const toggleNavbar = () => show.value = !show.value;      
      
      const goto = () =>{
        let to = {name: 'ContentSearched' , params:{query: query}}
        router.push(to);
      };
        
      return{
        show,
        toggleNavbar,
        goto,
        query
      }
    }
  }
</script>

ContentSearched.vue

<template>
   <div class="container">
     <BoxLink/>
    <main class="Main">
      <div class="alert alert-primary" role="alert">
        Resultados para "{{query}}"
      </div>
      <div v-if="isLoading">
        <!-- <img class="loading" src="../assets/loading.gif" alt="loading"> -->
      </div>
      <div v-else>
        <ul class="ListEpisodios AX Rows A06 C04 D02">
          <li v-for="(content, index) in contentSearched" :key="index">
            <div v-if="content.type === 'serie'">
              <Series :series="content"/>
            </div>
            <div v-if="content.type === 'pelicula'">
              <Movies :movies="content"/>
            </div>
          </li>
        </ul>
      </div>
    </main>
  </div>
</template>


<script>
  import {onCreated} from "vue-function-api"
  import {useState , useRouter , useStore} from '@u3u/vue-hooks';
  import BoxLink from "../components/BoxLink";
  import Movies from "../components/Movies";
  import Series from "../components/Series";

  export default{
    name: 'ContentSearched',
    components:{
      BoxLink,
      Movies,
      Series
    },
    setup(context){
      const store = useStore();
      const {route} = useRouter();

      const state = {
        ...useState(['contentSearched' , 'isLoading'])
      };

      const query = route.value.params.query;

      onCreated(() =>{
        store.value.dispatch('GET_CONTENT_SEARCH' , query.value);
      });
      return{
        ...state,
        query,
      }
    }
  };
</script>

R
Raf

This happened to me when I had a router-link pointing to the same route. e.g. /products/1.

The user is able to click on the products, but if a product was already clicked (and the component view was already loaded) and the user attempts to click it again, the error/warning shows in the console.

You can learn more on the github issue..

Posva, one of the main contributors of vue-router suggests:

router.push('your-path').catch(err => {})

However, if you don't want to have a catch block which does nothing, in order to solve the issue you can compare the router navigation with the current route and only navigate if they differ:

const path = `/products/${id}`
if (this.$route.path !== path) this.$router.push(path)

Note: $route is an object provided by vue-router to every component. See The Route Object for more info.


Note that this solution will also prevent you from going from /showPhoto?photo_id=1 to /showPhoto?photo_id=2. So it might be easier to just try/catch it -- see for more details stackoverflow.com/a/65326844
L
Luc125

I think the best solution to this problem can be implemented at the root level if we are not going to further use Router.push as asynchronous call.

import Router from 'vue-router';

const originalPush = Router.prototype.push;
Router.prototype.push = function push(location) {
  return originalPush.call(this, location).catch(err => err)
};

Vue.use(Router);

Halleluja! Though I had this error even when using router-link! On Github posva claims this workaround would be necessary only when using router.push() but not for router-link. Any Idea why I still get this error when using router-link (and get rid off when using your code)?
@TechNomad you can use $router.beforeEach' hook and in that use next(false) to terminate the navigation
@TechNomad, the NavigationDuplicated is generated prior to the $router.beforeEach is called.
f
fguillen

If you are not feeling comfortable catching all kind of errors, I think this implementation is more considerate:

this.$router.push("path").catch(error => {
  if (error.name != "NavigationDuplicated") {
    throw error;
  }
});

A
Alexandre Daubricourt

As of 2021, global configuration :

I only wanted to silence NavigationDuplicated error, an empty catch can be dangerous. So I did this :

const router = new VueRouter({/* ... */})

const originalPush = router.push
router.push = function push(location, onResolve, onReject)
{
    if (onResolve || onReject) {
        return originalPush.call(this, location, onResolve, onReject)
    }
 
    return originalPush.call(this, location).catch((err) => {
        if (VueRouter.isNavigationFailure(err)) {
            return err
        }
   
        return Promise.reject(err)
    })
}

Insert this once when you initialize vue-router. Thanks to @Oleg Abrazhaev for the update.


you can also specify the navigation failure type to ignore with if (VueRouter.isNavigationFailure(err, NavigationFailureType.duplicated)) {...} navigation failure type reference
c
cisordeng

I encountered the same problem while searching. My solution is to add timestamp to the this.$route.query parameters of the search page.

this.$router.push({
    path: "/search",
    query: {
      q: this.searchQuery,
      t: new Date().getTime(),
    }
  });

Hope it helps you.


Thanks, this is great answer. Other answers silence the NavigationDuplicated error, but this allows to actually navigate to the same route. That allows to navigate to the same route but with different params. In my case I needed that for unit testing.
Greate answer, direct and simple
I would say this solution is extremely smart.
Great and smart solution! This actually allows navigating to the same route again, which I wanted in my case.
C
Chan Yoong Hon

if you are using router.push in your code and you don't care about the navigation failing, you should catch it by using catch:

router.push('/location').catch(err => {})

R
Ruslan Semenov

in manual:

router.push(location, onComplete?, onAbort?)

You can use more simply

router.push("/", () => {});

This one should be the preferred, cleaner way
S
Soroush Chehresa

You mixed multiple concepts here from router-links to programmatic navigation, to query params to a state store. That makes it a bit difficult to help you and tell you what the "correct" solution here is.

Nonetheless, I think the best approach for you would be to: 1) define your route as

{
  path: "/search/:searchString",
  component: MySearchComponent,
  props: true
}

2) use a responsive <router-link> instead of your router.push

<input type="text" v-model="searchString">
<router-link :to="'/search/'+searchString" tag="button">search</router-link>

3) access the searchString in your search component via props: ['searchString'] and this.searchString

props: ['searchString'],
...
computed: {
  msg() {
    return `Searching for, ${this.searchString}!`;
  }
}

Full example: https://codesandbox.io/s/vue-routing-example-9zc6g
Note, I just forked the first codesandbox with a router I could find, adjust accordingly.


p
pacman

For TypeScript it worked like this

const superPush = VueRouter.prototype.push

VueRouter.prototype.push = async function push(loc:RawLocation):Promise<Route> {
  try {
    return await superPush.bind(this)(loc)
  } catch (e) {
    if (e?.name === 'NavigationDuplicated') {
      console.warn(e)
      return e
    } else {
      throw e
    }
  }
}

The question is not about TypeScript.
B
Bear Bones

I'm very late to the party, but I thought I'd add my solution to this issue as it isn't listed: I simply placed an intermediate searching page as a pass-through to the search results view. I am now using this page for doing some preprocessing of the search terms.

The page template simply is:

<template>
  <div>searching ...</div>
</template>  

The NavigationDuplicated error is now gone and as an added benefit because I perform the fetch in this intermediate page, the responsibility for error-handling is isolated from both the search bar and the results view.


M
Matteo Piazza

I post here the solution I found, because I was not able to find it well documented somewhere, and I went through it by trial and error. It could be useful to someone, or someone may fix my misunderstood interpretation of vue-router guards.

It make use of vue-router V4.x and a global beforeEach guard.

The use cases are:

user asks for https://app.com/ without being already authorized; user asks for https://app.com/ being already authorized; user asks for any available routing, which requires auth or not.

Routes:

const routes = [
  /**
   * Routes not requiring auth
   */
  {
    path: '/',
    component: () => import('layouts/NotAuthorizedLayout.vue'),
    children: [
      {
        path: 'login',
        name: 'LOGIN',
        component: () => import('pages/Login.vue') 
      },
      {
        path: 'emailsignup',
        component: () => import('pages/EmailSignup.vue') 
      },
      {
        path: 'forgottenpassword',
        component: () => import('pages/ForgottenPassword.vue') 
      }
    ]
  },

  /**
   * Routes requiring auth
   */
  {
    path: '/',
    component: () => import('layouts/AuthorizedLayout.vue'),
    meta: { requiresAuth: true },
    children: [
      { 
        path: 'authors',
        name: 'AUTHORS',
        component: () => import('pages/Authors.vue') 
      },
      { path: 'profile', component: () => import('pages/userProfile/index.vue') }
    ]
  }
];

beforeEach global guard:

  const redirectToLogin = route => {
    const LOGIN = 'LOGIN';
    if (route.name != LOGIN) {
      return { name: LOGIN, replace: true, query: { redirectFrom: route.fullPath } };
    }
  };

  const redirectToHome = route => {
    const DEFAULT = 'AUTHORS';
    return { name: DEFAULT, replace: true };
  };

  Router.beforeEach((to, from) => {
    const userIsAuthenticated = store.getters['authentication/userIsAuthenticated'];
    const requiresAuth = to.matched.some((route) => route.meta && route.meta.requiresAuth);

    if (!userIsAuthenticated && to.fullPath === '/') {
      return redirectToLogin(to);
    }

    if (!userIsAuthenticated && requiresAuth) {
      return redirectToLogin(to);
    }

    if (to.fullPath === '/') {
      return redirectToHome(to);
    }

    return true;
  });

G
Guest

Your question is rather old.

Your error is the "@click.prevent". This statement does not work, because your button is a submit button (so your event is called twice).

Use "@submit.prevent" should work (or change the type of your button).


Instead of changing the template, you can use "prevent default" in your "goto()" method.
C
ChromeKK

My solution is a mix of extending prototype with check Navigation Duplicated Error. Other errors and warning should be visible. After a week on production - no NavigationDuplicated and everything is working.

import { equals } from 'ramda'

export function register(Vue) {
  const routerPush = Router.prototype.push
  const routerReplace = Router.prototype.push

  const isNavigationDuplicated = (currentRoute, nextRoute) => {
    const { name: nextName, params: nextParams = {}, query: nextQuery = {} } = nextRoute
    const { name, params, query } = currentRoute

    return equals(nextQuery, query) && equals(nextParams, params) && equals(nextName, name)
  }

  Router.prototype.push = function push(location) {
    if (!isNavigationDuplicated(this.currentRoute, location)) {
      return routerPush.call(this, location)
    }
  }

  Router.prototype.replace = function replace(location) {
    if (!isNavigationDuplicated(this.currentRoute, location)) {
      return routerReplace.call(this, location)
    }
  }

  Vue.use(Router)
}

m
marc_s

I noticed that error comes up when I tried to replace url query parameter with same value.

I have select filters and url query string params are in sync with their values. It works well as long as you change to a new value. If value remains the same (for example coming back from history) and thus replacing query string parameter with same value, error pops out.

Solution was to check if value is changed, and then replace query param in router:

let newValue = 'foo'; // new query value for parameter
let qcopy = { ...this.$route.query }; // clone current query
// prevent NavigationDuplicated: Avoided redundant navigation to current location
if (qcopy['your_param'] != newValue){
  qcopy['your_param'] = newValue;
  this.$router.replace({query: qcopy});
}

M
MarsAndBack

Stop the click propagation from hitting the router action

I have a high-level answer here that may be useful to others. It may not directly answer the OP, but this thinking applies.

My take on this is to NOT fiddle around with global configuration or attempt to catch router logic. It doesn't make sense to pollute your app with exceptions all over the place.

In the case of a results view with filters. If the filters happen to be presented as router links (for various reasons), but we don't want actual router logic to occur when they're clicked.

So, capture the click before it gets to the router action!

Then you get the best of both worlds:

Search filters (as a link) that...

Do logic within a view Still provide the benefit of being presented as a link (benefits to bot scans, user convenience and accessibility, etc)

Technique:

Use @click.prevent on a child inside the router-link, to capture and stop the click from hitting the router.

Example:

Before: Router logic occurs, even though we're in the route already

<router-link class="nav-link" :to="{name: 'MovieSection'}" >
    <i class="fas fa-film"></i>Peliculas
</router-link>

After: Router logic inhibited, we run other logic (applyFilter)

<router-link class="nav-link" :to="{name: 'MovieSection'}" >
    <div @click.prevent="myFunc(something)">
        <i class="fas fa-film"></i>Peliculas
    </div>
</router-link>

With this approach you may avoid messy high-level exceptions in your app.


Y
Yair V.

Best practice would be:

import { isNavigationFailure, NavigationFailureType } from 'vue-router/src/util/errors';    
this.$router.push(...).catch((error) => {
      if (!isNavigationFailure(error, NavigationFailureType.duplicated))
        throw error
    );
  }

See the Vue Router docs


d
dovetalk

Here's is a simple and efficient solution:

if(from.fullPath === to.fullPath){
    return
}

@hero592 Please read through the code of conduct and refrain from making such comments. We all are here to help others and not indulge in such unacceptable practices.