How to fix vue-router rendering wrong component on correct route

vue-router

#1

I am setting up a vuejs application that should route to some predefined pages.
All the routes work perfect, except “complete” and “allCompleted”

When all the quests are completed, the user should be redirected to the allCompleted route.

What the actual result is:

When a user completes a quest, the app gets redirected to “complete”. But this renders the allCompleted component.

When all quests are completed, we get redirected to “allCompleted”.

So I don’t know what I’m doing wrong.

We think we’ve read every post on the internet regarding vue-router and rendering wrong components and most of them are about typo’s or rendering children in a sub-router-view. But this is not the case in our application.

router.ts

import Vue from "vue";
import Router from "vue-router";
import { QuestsView, QuestCompleteView } from "@/modules/quests";
import QuestDetailView from "../modules/quests/views/QuestDetailView.vue";
import QuestAllCompletedView from "../modules/quests/views/QuestAllCompletedView.vue";

import Register from "../modules/auth/components/Register.vue";
import Login from "../modules/auth/components/Login.vue";
import PasswordForgotten from "../modules/auth/components/PasswordForgotten.vue";
import ContactsView from "../modules/contacts/views/ContactsView.vue";
import authGuard from "./auth-guard";
import LeaderBoardView from "@/modules/leaderboard/views/LeaderBoardView.vue";

Vue.use(Router);

export default new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "*",
      redirect: "/quests"
    },
    {
      path: "/register",
      name: "register",
      component: Register
    },
    {
      path: "/login",
      name: "login",
      component: Login
    },
    {
      path: "/passwordForgotten",
      name: "passwordForgotten",
      component: PasswordForgotten
    },
    {
      path: "/quests",
      name: "quests",
      component: QuestsView,
      beforeEnter: authGuard
    },
    {
      path: "/quests/:id",
      name: "quest detail",
      component: QuestDetailView,
      beforeEnter: authGuard
    },
    {
      path: "/contacts",
      name: "contacts",
      component: ContactsView,
      beforeEnter: authGuard
    },
    {
      path: "/info",
      name: "info",
      beforeEnter: authGuard
    },
    {
      path: "/leaderboard",
      name: "leaderboard",
      component: LeaderBoardView,
      beforeEnter: authGuard
    },
    {
      path: "/complete",
      name: "complete",
      component: QuestCompleteView,
    },
    {
      path: "/allCompleted",
      name: "allCompleted",
      component: QuestAllCompletedView
    }
  ]
});

QuestList.vue

export default class Quests extends Vue {
  @QuestsGetter quests: Quest[];
  @QuestsGetter loading: boolean;
  @QuestsAction loadQuests;

  @Watch("quests")
  onQuestsChange(value: Quest[]): void {
    const completedQuests = value.filter(
      quest => quest.status === QuestStatus.COMPLETED
    );
    if (completedQuests.length === this.quests.length) {
      this.$router.push("/allCompleted"); // Todo: Create allCompleted view
    }
  }

  created() {
    this.loadQuests();
  }
}
</script>

QuestAllCompletedView.vue

<template>
  <div class="quest-all-complete-view">
    <router-link to="quests">
      <div class="all-reward all-reward--image"></div>
    </router-link>
  </div>
</template>

<script lang="ts">
import { Vue } from "vue-property-decorator";

export default class QuestAllCompletedView extends Vue {}
</script>

<style scoped lang="scss">
@import "~@/styles/variables";
.quest-all-complete-view {
  width: 100%;

  .all-reward {
    width: auto;
    height: 56rem;

    &--image {
      background: url("~@/assets/images/reward-starburst-final.png") center
        center / contain no-repeat;
    }
  }

  .back-to-overview {
    height: 4rem;
    display: block;
    margin: -140px auto 0 auto;

    &--image {
      background: url("~@/assets/images/backtooverview.png") center center /
        contain no-repeat;
    }
  }
}
</style>

QuestCompleteView.vue

<template>
  <div class="quest-complete-view">
    <div class="reward reward--image"></div>

    <router-link to="quests">
      <div class="back-to-overview back-to-overview--image"></div>
    </router-link>
  </div>
</template>

<script lang="ts">
import { Vue } from "vue-property-decorator";

export default class QuestComplete extends Vue {}
</script>

<style scoped lang="scss">
@import "~@/styles/variables";
.quest-complete-view {
  width: 100%;

  .reward {
    width: auto;
    height: 56rem;

    &--image {
      background: url("~@/assets/images/reward.png") center center / contain
        no-repeat;
    }
  }

  .back-to-overview {
    height: 4rem;
    display: block;
    margin: -140px auto 0 auto;

    &--image {
      background: url("~@/assets/images/backtooverview.png") center center /
        contain no-repeat;
    }
  }
}
</style>

Auth-guard

import store from "@/store/index";

export default (to: any, from: any, next: any) => {
  if (store.state.auth.user) {
    next();
  } else {
    next("/login");
  }
};

Part of the code in the QuestDetailView.vue that routes to /complete

public handleButtonClick(): void {
    if (this.quest.status === QuestStatus.UNCOMPLETED) {
      this.startQuest(this.quest.id).then(() => this.$router.push("/quests"));
    } else if (this.quest.status === QuestStatus.IN_PROGRESS) {
      this.stopQuest(this.quest.id).then(() => this.$router.push("/complete"));
    }
}

So what we want is that:
When we route to the /complete route, it renders the Questcomplete component, being “QuestCompleteView”

When we do render.push("/allCompleted"), we go to the allCompleted route with the correct component, being QuestAllCompletedView


#2

First of all, the wildcard route should always be the last route, not the first (or anywhere in the middle…).

Second, what does the component look like that contains the router-view?


#3

This is the App.vue file containing the router-view

router-view.vue

 <template>
  <div class="app">
    <main><router-view /></main>
    <tab-bar v-if="isAuthenticated" />
  </div>
</template>

<style lang="scss">
@import "styles/main";
</style>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { TabBar } from "@/modules/navigation";

@Component({
  components: { TabBar }
})
export default class App extends Vue {
  get isAuthenticated(): boolean {
    return !!this.$store.state.auth.user;
  }
}
</script>

#4

@LinusBorg I’ve changed the order of the routes, but that didn’t fix it, I’m afraid.


#5

In that case I have no idea what’s going on and would have to see the code running.


#6

I’ll try to set up a codesandbox asap