Router link with transition wont work second time: - vue-router

routes.js
const routes = [
{
path: "/",
component: Home,
meta: {
title: 'Home'
}
},
{
path : "/register",
component: Register,
meta: {
title: 'Register'
}
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
App.vue
<template>
<router-link to="home">Home</router-link>
<router-link to="register">Register</router-link>
<router-view v-slot="{ Component }">
<transition name='fade' mode="out-in">
<component :is="Component"></component>
</transition>
</router-view>
</template>
When I visit /home and click on the Register - it works. But once I try to go back to Home, it's clearly loading the component but it's blank white.
From then on switching between routes obviously switches components, but everything stays blank white until I refresh on any page - then it shows it.
When I remove the Transition from the router-view it starts working.
With the transition the only switch that is working is going from Home to Register. Then any switches just turn the page blank white.
I've tried with $router.push, router-link, $router.back() - all same result.
EDIT: I found out that not only removing the whole transition fixes it, but also changing from out-in to in-out. It doesn't look good with my design, but it shows the components fine.

I tried the code you pasted, it works well...
Have you added any styles to make it work, if not please add the styles, i add styles like below:
<style scoped lang="scss">
.fade-enter-active,
.fade-leave-active {
transition: opacity 1s ease;
}
.fade-enter-from,
.fade-leave-active {
opacity: 0;
}
</style>

Related

How to append #click navigateTo() event to v-list items props

I am using Nuxt3 with Vuetify3, and I am having trouble getting this to work:
<template>
<v-navigation-drawer
v-model="menu"
class="pa-4"
temporary
>
<v-list
:items="items"
nav
density="compact"
/>
</v-navigation-drawer>
</template>
<script setup lang="ts">
const menu = ref(false);
const items = [
{
title: 'Dashboard',
props: {
'#click': () => {
navigateTo('./dashboard');
},
prependIcon: 'mdi-view-dashboard-variant-outline'
}
},
{
title: 'Orders',
props: {
to: './orders',
prependIcon: 'mdi-package-variant'
}
}
];
</script>
The Orders list-item works as expected, but it is causing other bugs in my app with Nuxt, so I would like to use Nuxt's navigateTo() function. The Dashboard link doesn't have the click event attached.
I have also tried '#click': navigateTo('./dashboard') but this creates an endless navigation loop in Nuxt. I also tried 'v-on:click' and click () {}, but I can't seem to get it to append the click event to the v-list-items.
Obviously I could rewrite this without the shorthand <v-list :items="items" />, but I'm sure there must be a way to do this shorthand.

New Router page doesn't load the component until manually reloading the entire page

I added a new route, and added an animation in transition to the new route.
I added the following code which pushes the new route (/first) when a button is clicked:
/* From the Template */
<router-view #clickedNext1="onClickTransition" v-slot="{ Component }">
<transition name="route1" mode="out-in">
<component :is="Component"></component>
</transition>
</router-view>
/* From the Script */
methods: {
onClickTransition() {
this.$router.push("/first");
},
Now the problem is that when I click the button and invoke the "onClickTransition" method, the router seems to be pushed just fine, but the page is empty. The components are rendered only when I manually refresh the page by pressing ctrl+R.
I believe the problem perhaps comes from insertion of the animation, but if I refresh the page manually, the animation works just fine. So I have no idea what the problem is. I will be very grateful for help.
Here is the rest of the code for app.vue:
<template>
<router-view #clickedNext1="onClickTransition" v-slot="{ Component }">
<transition :key="$route.fullPath" name="route1" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</template>
<script>
export default {
name: "App",
components: {},
data() {
return {};
},
methods: {
onClickTransition() {
this.$router.push("/first");
},
leave(event) {
event.preventDefault();
event.returnValue = "";
},
},
mounted() {
window.addEventListener("beforeunload", this.leave);
},
beforeUnmount() {
window.removeEventListener("beforeunload", this.leave);
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #fff;
background-color: #151515;
position: relative;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
margin: 0px;
}
/* route transition */
.route1-enter-from {
opacity: 0;
}
.route1-enter-active {
transition: all 3s ease-in;
}
.route1-leave-to {
opacity: 0;
}
.route1-leave-active {
transition: all 3s ease-in;
}
</style>
code section from index.js:
import { createRouter, createWebHistory } from "vue-router";
import MainPage from "../views/MainPage.vue";
import FirstScene from "../views/FirstScene.vue";
const routes = [
{
path: "/",
name: "main",
component: MainPage,
},
{
path: "/first",
name: "first",
component: FirstScene,
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
The "onClickTransition" method comes from the "PreStartPage.vue" component, which is the child component of "MainPage.vue" which is the main route.
Once the "Next" button is clicked in the "PreStartPage.vue", it sends an event to the "MainPage.vue" with this.$emit."MainPage.vue" then receives the event with a method named "onClickNext1", which sends out a signal to "App.vue" with another this.$emit. That is where the "#clickedNext1" that is shown in the App.vue comes from.
Here is the code from "PreStartPage.vue":
<script>
export default {
name: "PreStartPage",
methods: {
onClickNext() {
this.$emit("clickedNext");
},
},
};
</script>
Here is the code from "MainPage.vue":
<script>
import PreStartPage from "../components/PreStartPage.vue";
export default {
name: "MainPage",
components: { PreStartPage },
data() {
return { showMain: true, showPre: false };
},
methods: {
toggleMain() {
this.showMain = !this.showMain;
this.showPre = !this.showPre;
},
onClickNext1() {
this.$emit("clickedNext1");
},
},
};
</script>
Try modifying your code like this:
/* From the Template */
<router-view #clickedNext1="onClickTransition" v-slot="{ Component }">
<transition :key="$route.fullPath" name="route1" mode="out-in">
<component :is="Component"></component>
</transition>
</router-view>
The "key" property set to $route.fullPath should ensure that the transition is done correctly whenever the route is changed.
EDIT
To solve this, you can add a ":enter-active-class" and ":leave-active-class" property to the transition component, which allows you to specify the class that should be applied to the element during the transition.
In your App.vue component, you can update the transition component like this:
<transition :key="$route.fullPath" name="route1" mode="out-in" :enter-active-class="'route1-enter-active'" :leave-active-class="'route1-leave-active'">
<component :is="Component" />
</transition>
This will ensure that the correct classes are applied to the element during the transition, and that the components are fully rendered before the animation starts.
For more info i should you to visit the official wiki: https://vuejs.org/guide/built-ins/transition.html#css-based-transitions
You can try using
created() {
this.$watch(() => this.$route.params, () => {
// WATCH FOR ROUTE CHANGES
},
}

Simple Nuxt 3 Page Transition not working

I'm discovering Nuxt 3 and and simply want to make an animation between pages. The idea is to use javascript hooks to make page transitions using js library such as gsap or animeJs.
So in my app.vue file, I simply put <NuxtPage/> into <Transition> element like this :
<NuxtLayout>
<Transition>
<NuxtPage/>
</Transition>
</NuxtLayout>
My vue pages ('./pages/index.vue' and './pages/project/myproject.vue') look like this :
<template>
<div>
<h1>My Project</h1>
</div>
</template>
<script setup>
function onEnter(el, done) {
done()
}
function onLeave(el, done) {
done()
}
</script>
I have followed both Nuxt 3 and Vue 3 documentations :
https://v3.nuxtjs.org/guide/directory-structure/pages#layouttransition-and-pagetransition
https://vuejs.org/guide/built-ins/transition.html#javascript-hooks
I also read this thread on github, but I can't find answer :
https://github.com/nuxt/framework/discussions/851
When i was using Nuxt 2 I only need to put transition object into my page like this and it's working fine :
<script>
export default {
// ... (datas, methods)
transition: {
mode: "in-out",
css: false,
enter(el, done) {
console.log("enter");
done()
},
leave(el, done) {
console.log("leave");
done()
}
}
}
</script>
<template>
<div>
<h1 class="text-center text-5xl">Hello World</h1>
</div>
</template>
Do you have any idea how to achieve it ?
Nuxt 3 doesn't need a <Transition> wrapper around pages/layouts, by default it does that for you.
Take a look at this starter template: in assets/sass/app.scss, the last part of the style is page and layout transition.
You can tweak the default named animations (page- and layout-).
More infos here
Just follow the official documentation for Nuxt 3. You need to add the following code to your nuxt.config.ts file:
export default defineNuxtConfig({
app: {
pageTransition: { name: 'page', mode: 'out-in' }
},
})
And then apply the classes inside your app.vue file, like this:
<template>
<NuxtPage />
</template>
<style>
.page-enter-active,
.page-leave-active {
transition: all 0.4s;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
filter: blur(1rem);
}
</style>
Nuxt 3 uses the Vue's <Transition> component under the hood, so you don't need to add it in the template.
Be careful with the css prefix.

Passing ID through router-link in Vue.js

I have 2 router links that link to the same page (definition page) but has different ids, in my definition page I have an if else loop that checks the id and then posts the apropriate definition for that id.my problem is that my loop can't properly read my id and goes straight to my else statment, this is the closest that I've gotten it to work.
My 2 router-links in page 1
<router-link :to="{ path: '/Pulse/Definition',id:'Alignment'}" v-bind:tooltip="Alignment" append><a >Read more ></a></router-link>
<router-link :to="{ path: '/Pulse/Definition'}" id="Trust" append><a >Read more ></a></router-link>
My definition page
<template>
<div class="PulseDefinition page row">
<h2 v-if=" id=='Alignment'">hello world {{id}}</h2>
<h2 v-else-if=" id=='Trust'">hello world {{id}}</h2>
<h2 v-else>Sorry try again</h2>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
.PulseDefinition{
margin-top:2.5rem;
margin-left:3rem;
background-color: aquamarine;
width:50rem;
height:50rem;
}
</style>
Router
import Vue from 'vue';
import Router from 'vue-router';
import Community from '../components/PulseCommunity';
import Home from '../components/Home';
import Definition from '../components/Definition.vue';
Vue.use(Router)
export default new Router({
routes:[
{
path:'Tuba',
name:'Tuba',
component: Default
},
{
path:'/Pulse',
name:'Pulse',
component:PulseNav,
children:[{
path:'/Pulse/Overview',
name:'Overview',
component:Overview
},
{
path:'/Pulse/Personal',
name:'Personal',
component:Personal
},
{
path:'/Pulse/Community',
name:'Community',
component:Community
},
{
path:'/Pulse/Definition/:id',
name:'Pulse Definition',
component:Definition
}
]
},
{
path:'/Coaching',
name:'Coaching',
component:Coaching
},
{
path:'/Comunication',
name:'Comunication',
component:Comunication
},
{
path:'/Home',
name:'Home',
component:Home
},
]
})
Normally when your using the router inside of a Vue application you'll want to use route parameters, check out the dynamic routing link here.
Using the same example:
const router = new VueRouter({
routes: [
// dynamic segments start with a colon
{ path: '/user/:id', component: User }
]
})
Here in our router whenever we navigate to a url where /user/ is present providing we then add something after we can match the /:id section of it. Then inside of our component we are able to query the parameters for the ID that was sent in our url:
console.log(this.$route.query.id)
Using this we could then save that value into our component or we could build reactivity around this.$route.query.
In your case you'd only need to append to the string that you pass into that router link by simply using your data / methods or if you require further rules you could use a computed method. This might become or something simmilar:
<router-link :to="{ path: '/Pulse/Definition'+ this.alignmentType}" v-bind:tooltip="Alignment" append><a >Read more ></a></router-link>
i found a solution thx to the help of li x and a senior coworker of mine,here is the awnser.
my working router-link in page 1
<router-link :to="{ path: '/Pulse/Definition/'+'Alignment'}" v-bind:tooltip="Alignment" append><a >Read more ></a></router-link>
im adding the id(Alignment) to my url with[:to="{ path: '/Pulse/Definition/'+'Alignment'}"]
my definition page
<template>
<div class="PulseDefinition page row">
<h2 v-if=" this.$route.params.id=='Alignment'">hello world {{this.$route.params.id}}</h2>
<h2 v-else-if=" this.$route.params.id=='Trust'">hello world {{this.$route.params.id}}</h2>
<h2 v-else-if=" this.$route.params.id=='undefined'">Sorry try again {{this.$route.params.id}}</h2>
<h2 v-else>XXXSorry try againXXX{{this.$route.params.id}}</h2>
<!-- {{console.log("hi")}} -->
</div>
</template>
<script>
// console.log(this.$route.query.id);
export default {
}
</script>
im using [this.$route.params.id] to retrieve my id, and my router page stayed the same.
thank you all for the great help ;)

vuejs application with different layouts (e.g. login layout, page layout, signup etc.)

I generated a project using vue-cli. I see project has one App.vue which is kinda main layout of the app - if I'm not mistaken. Here I put my basic HTML layout and <router-view></router-view>. Now the issue is that I need completely different layout for login (different wrappers , body has different classes) but I can't change it since App.vue has template which is kinda "fixed" as a layout. How to approach this issue? Is there recommended way?
Should I create new component that represents layout so in that case my App.vue template would only have <router-view></router-view> and then LoginLayout.vue would be included into it?
I think I found a solution. The approach has App.vue containing only <router-view></router-view> and then including different components that represent layout (if needed, containing <router-view> and subroutes). I found a project using it in that way here.
I think it keeps things more clean and organised. IMHO, hiding all elements which define layout structure (all the divs) would be too messy - especially for bigger apps.
A nice solution for this is using slots
First create your "layout component"
src/components/layouts/basic.vue
<template>
<div class="basic-layout">
<header>[Company logo]</header>
<hr>
<slot/>
<hr>
<footer>
Made with ❤ at Acme
</footer>
</div>
</template>
Then use it in another component:
<template>
<layout-basic>
<p>Hello world!</p>
</layout-basic>
</template>
<script>
import LayoutBasic from '#/components/layouts/basic'
export default {
components: {
LayoutBasic
}
}
</script>
"Hello world" will appear where the <slot/> tag is.
You can also have multiple slots with names, see the complete docs.
I find another solution by using router meta. I just have a few components need another layout.
I added a plainLayout meta key in src/router/index.js.
export default new Router({
mode: 'history',
linkExactActiveClass: 'app-head-menu--active',
routes: [
{
path: '/',
component: Features,
},
{
path: '/comics/:id',
component: Comic,
props: true,
},
{
path: '/comics/:comic_id/:chapter_index',
component: Chapter,
props: true,
meta: {
plainLayout: true,
},
},
],
});
Then render layout conditionally with playLayout in src/App.vue.
<template>
<div>
<div v-if="!$route.meta.plainLayout">
<div class="app-head">
</div>
<div class="app-content">
<router-view/>
</div>
</div>
<div v-if="$route.meta.plainLayout">
<router-view/>
</div>
</div>
</template>
<script>
export default {
name: 'app',
};
</script>
See a demo project here.
Utilizing Routes, and in particular, children routes is a great way to approach having common layouts in Vue.
All of this code is utilizing Vue 2.x
Start by having a really simple vue component called App that has no layout.
app.vue
<template>
<router-view></router-view>
</template>
Then have a Routes file that you'll bring into your Vue instance.
Routes.(ts|js)
import Vue from 'vue'
import VueRouter from 'vue-router'
const NotFoundComponent = () => import('./components/global/notfound.vue')
const Login = () => import('./components/account/login.vue')
const Catalog = () => import('./components/catalog/catalog.vue')
export default new VueRouter({
mode: 'history',
linkActiveClass: 'is-active',
routes: [
//Account
{ path: '/account', component: () => import('./components/account/layout.vue'),
children: [
{ path: '', component: Login },
{ path: 'login', component: Login, alias: '/login' },
{ path: 'logout',
beforeEnter (to: any, from: any, next: any) {
//do logout logic
next('/');
}
},
{ path: 'register', component: () => import('./components/account/register.vue') }
]
},
//Catalog (last because want NotFound to use catalog's layout)
{ path: '/', component: () => import('./components/catalog/layout.vue'),
children: [
{ path: '', component: Catalog },
{ path: 'catalog', component: Catalog },
{ path: 'category/:id', component: () => import('./components/catalog/category.vue') },
{ path: 'product', component: () => import('./components/catalog/product.vue') },
{ path: 'search', component: () => import(`./components/catalog/search.vue`)} ,
{ path: 'basket', component: () => import(`./components/catalog/basket.vue`)} ,
{ path: '*', component: NotFoundComponent }
]
}
]
})
The code is using lazy loading (with webpack) so don't let the () => import(...) throw you. It could have just been import(...) if you wanted eager loading.
The important bit is the children routes. So we set the main path of /account to utilize the /components/account/layout.vue but then the very first two children specify the main content vue (Login). I chose to do it this way because if someone just browses to /account I want to greet them with the login screen. It may be appropriate for your app that /account would be a landing page where they could check the order history, change passwords, etc...
I did the same thing for catalog... / and /catalog both load the catalog/layout with the /catalog/catalog file.
Also notice that if you don't like the idea of having "subfolders" (i.e. account/login instead of just /login) then you can have aliases as I show in the login.
By adding , alias: '/login' it means users can browse to /login even though the actual route is /account/login.
That is the key to the whole thing, but just to try and make the example complete...
Here is my boot file which hooks up my app.vue and routes:
boot.(ts|js)
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import App from './components/app.vue';
import router from './routes';
new Vue({
el: '#app',
router,
render: h => h(App)
});
I created a layout.vue file for each of my main sections of my app (account, catalog, etc).
account/layout.vue
<template>
<div>
<cc-header></cc-header>
<div class="container">
<main>
<router-view></router-view>
</main>
<aside>
</aside>
</div>
<cc-footer></cc-footer>
</div>
</template>
<script lang="ts">
import ccHeader from "../common/cc-header.vue"
import ccFooter from "../common/cc-footer.vue"
export default {
components: {
ccHeader,
ccFooter
}
}
</script>
<style lang="scss" scoped>
.container {
display: flex;
}
main {
flex: 3;
order: 2;
}
aside {
flex: 1;
order: 1;
}
</style>
And the layout for catalog...
catalog/layout.vue
<template>
<div>
<cc-header></cc-header>
<div class="catalog-container">
<main class="catalog">
<router-view></router-view>
</main>
<cc-categories></cc-categories>
</div>
<cc-footer></cc-footer>
</div>
</template>
<script lang="ts">
import ccHeader from "../common/cc-header.vue"
import ccFooter from "../common/cc-footer.vue"
import ccCategories from "./cc-categories.vue"
export default {
components: {
ccCategories,
ccHeader,
ccFooter
},
data : function() : any {
return {
search: ''
}
},
}
</script>
<style lang="scss" scoped>
.catalog-container {
display: flex;
}
.category-nav {
flex: 1;
order: 1;
}
.catalog {
flex: 3;
order: 2;
}
</style>
Both layouts use common components like header and footer, but they don't need to. The catalog layout has categories in the side nav, while the account layout doesn't. I put my common components under components/common.
common/footer.vue
<template>
<div>
<hr />
<footer>
<div class="footer-copyright">
<div>© Copyright {{year}} GlobalCove Technologies, LLC</div>
<div>All rights reserved. Powered by CoveCommerce.</div>
</div>
</footer>
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.component('cc-footer', {
data : function() : any {
return {
year: new Date().getFullYear()
}
},
})
</script>
<style lang="scss">
</style>
Overall file structure
src/
boot.ts
routes.ts
components/
app.vue
catalog/
layout.vue
catalog.vue
category.vue
product.vue
search.vue
basket.vue
account/
layout.vue
login.vue
register.vue
global/
notfound.vue
common/
cc-header.vue
cc-footer.vue
The combination of routes, a plain app.vue, and specific layout files, along with common components should get you to where you want to be.
I route my apps through a layout. Eg login requires no structure, just the login component, but other pages require, header footer etc, so here is an example of how I do this in my routes:
// application routes
'/secure': {
name: 'secure',
component: require('../components/layouts/default'),
subRoutes: {
'/home': {
name: 'home',
component: require('../components/home/index')
}
}
}
//- public routes
'/insecure': {
name: 'insecure',
component: require('../components/layouts/full-bleed'),
subRoutes: {
'/login': {
name: 'login',
component: require('../components/session/login')
}
}
}
Both of these layout templates have a router-view tag, so you can them build your layouts as you require for different parts of the app.
I dynamically check the route globally on App.vue and use that to determine what needs to be shown.
App.vue
<template>
<div id="app">
<top :show="show" v-if="show.header"></top>
<main>
<router-view></router-view>
</main>
<bottom v-if="show.footer"></bottom>
</div>
</template>
<script>
export default {
mounted: function() {
if(window.location.hash == "#/" || window.location.hash.indexOf('route')) {
vm.show.header = true
vm.show.footer = true
vm.show.slideNav = true
}
}
watch: {
$route: function() {
// Control the Nav when the route changes
if(window.location.hash == "#/" || window.location.hash.indexOf('route')) {
vm.show.header = true
vm.show.footer = true
vm.show.slideNav = true
}
}
}
}
</script>
That way I'm also able to control what's shown in the top and bottom navs through props.
Hope this helps!
I don't know about any "recommended way" but my app is structured like this:
App.vue - just top menu bar (which is not rendered when user is not authenticated) and <router-view></router-view> for each component (page)
So every page could have totally different layouts.
Comment to the accepted answer
Kind of disagree with this. Had the same issue and this answer confused me. Basically when you have a component which you'd like to reuse everywhere (e.g. footer, header) in your application then you can keep it in the App.vue. It was my case, I wanted to have footer and header in every page, finding this answer put me into the wrong direction, but you can do it and it does works, for example App.vue:
<template>
<div id="app">
<app-header />
<router-view />
<app-footer />
</div>
</template>
<script lang="ts">
// Imports related to Vue.js core.
import { Component, Vue } from "vue-property-decorator";
// Imports related with custom logic.
import FooterComponent from "#/components/Footer.vue";
import HeaderComponent from "#/components/Header.vue";
#Component({
components: {
"app-footer": FooterComponent,
"app-header": HeaderComponent
}
})
export default class App extends Vue {}
</script>
<style lang="scss" scoped>
</style>
Footer.vue (located in components/Footer.vue):
<template>
<div>
<footer>
<div>© {{ year }} MyCompany</div>
</footer>
</div>
</template>
<script lang="ts">
// Imports related to Vue.js core.
import { Component, Vue } from "vue-property-decorator";
#Component({})
export default class FooterComponent extends Vue {
public year = new Date().getFullYear();
}
</script>
<style lang="scss" scoped>
</style>
Header.vue (located in components/Header.vue):
<template>
<div>
<header>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-link to="/contact">Contact</router-link>
</header>
</div>
</template>
<script lang="ts">
// Imports related to Vue.js core.
import { Component, Vue } from "vue-property-decorator";
#Component({})
export default class HeaderComponent extends Vue {}
</script>
<style lang="scss" scoped>
</style>