Vue Router is duplicating entries in window.history in Safari - vue.js

My App.vue has the following setup:
// App.vue
<template>
<div id="app">
<router-view/>
</div>
</template>
And Home.vue has link to Gmap.vue:
// Home.vue
<template>
<div>
<router-link to="/gmap">vue2-google-maps</router-link>
</div>
</template>
And Gmap.vue has <GmapMap> component from vue2-google-maps:
// Gmap.vue
<template>
<div>
<GmapMap :center="{lat: 0, lng: 0}" :zoom="10">
</GmapMap>
</div>
</template>
Finally router.js is like this:
export default new Router({
mode: "history",
routes: [
{
path: "/",
name: "home",
component: Home
},
{
path: "/gmap",
name: "gmap",
component: Gmap
}
]
})
In Safari, at path /, when click <router-link>, Safari navigates to /gmap and puts /gmap path into Safari's window.history. So clicking Safari's back button will make Safari navigate back to /.
Then clicking <router-link> again will make Safari navigate to /gmap. However, at this time Safari will put /gmap path into Safari's window.history twice (first /gmap and second /gmap). So clicking Safari's back button won't make Safari navigate to /. Instead, Safari will navigate to the first /gmap.
I confirmed that:
when Safari went back and forward between first /gmap and second /gmap, popstate event wasn't triggered, and Vue Router didn't catch the changes so none of Navigation Guards were invoked.
this happens in Safari (macOS 10.13.6 and iOS 11.4.1)
this doesn't happen if routing to components that don't contain <GmapMap> component
this doesn't happen when routing without Vue Router
this doesn't happen in Chrome 67 (macOS 10.13.6 and iOS 11.4.1)
Why does Safari put same path twice?

Though not an exact answer, to solve the issue wrap <keep-alive> around <router-view> in App.vue:
// App.vue
<template>
<div id="app">
<!-- Note that <router-view> is wrapped by <keep-alive> -->
<keep-alive>
<router-view />
</keep-alive>
</div>
</template>
Source: GitHub discussion.

Related

In Vue Route, the parent path component display with the child path component

I have 2 components, AboutView.vue and HomeView.vue.
AboutView.vue
<template>
<div class="about">
<h1>This is an about page</h1>
<router-link to="/about/add">Add</router-link>
<router-view></router-view>
</div>
</template>
HomeView.vue
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
When I click the 'Add' button, I will be redirected to /about/add where /add is config inside index.js
{
path: '/about',
name: 'about',
component: AboutView,
children:[
{
path: 'add',
name: 'Add',
component: ()=> import ('../views/HomeView.vue'),
},
]
}
The path is working fine.
But the problem is when I click the 'Add' button, what I would expect is it brought me to the HomeView.vue, but instead, it showed the HomeView.vue below the Add button.
Here is the screen.
ScreenShot
it seems like the route-view is a part of the template, but I want it to display the HomeView.vue instead of AboutView + HomeView.
I tried to use <router-view name='name></router-view>, which doesn't help much. I can write a js function to check the path and make the code above disappear, but I expect something more professional.
Let's try to understand from the below image-
router-view is basically the view where the components are rendered.
It’s like the main div that contains all the components, and it
returns the component that matches the current route.
So, to fix this just remove the router-view from AboutView.vue component, so when you will redirect to /home, the component HomeView.vue, will render inside App.vue's router-view.

Vue subrouting with navbar and sidebar

I'm trying to set up a routing system with vue. For my purpose, I need a fixed navbar on the top that needs to be displayed on every page and a sidebar that I want to display only on the settings page. Following the documentation I tried:
const routes = [
{
path: '/settings',
name: 'Settings',
component: Settings,
children: [
{
path: 'route1',
name: 'Route1',
component: Route1
},
{
path: 'route2',
name: 'Route2',
component: Route2
}
]
}
]
Then on the settings template:
<template>
<div class="flex items-start">
<div class="lg:w-3/12 w-12 sm:w-16 md:w-24 pb-10 lg:pr-8">
<Sidebar />
</div>
</div>
<div class="lg:w-9/12 w-full pt-10 pb-8 text-justify">
// My subroute goes here
</div>
</template>
I feel that I'm missing something. First, I can't understand how to properly display the subroutes. I tried with <router-view /> but it seems to refer to the parent navigation.
Second, I don't want the user to visit the /settings route but only /settings/route1 and settings/route2.
I can achieve this by simply adding the sidebar in every settings route but this seems bad because it forces the <Sidebar/> component to be mounted every time
Where am I wrong?
Thanks
As you probably have guessed, the <router-view /> element goes in your Settings component:
<template>
<div class="flex items-start">
<div class="lg:w-3/12 w-12 sm:w-16 md:w-24 pb-10 lg:pr-8">
<Sidebar />
</div>
</div>
<div class="lg:w-9/12 w-full pt-10 pb-8 text-justify">
<router-view /> <!-- Here is your router view -->
</div>
</template>
Then as it was pointed out in the comments, /settings will always be a valid route.
What you can do when the client directly navigates to /settings is to replace the current route with one of the two children (possibly based on some logic) in the mounted hook:
mounted() {
if(this.$router.currentRoute.path.endsWith('/settings')) {
this.$router.replace('/settings/route1')
}
}
Or use $router.push() instead based on what you want the navigation history to look like.

Nuxt.js could not found component with errors "Failed to mount component: template or render function not defined."

I am having problems with my Nuxt.js site.
I have defined a page, with a dynamic slug param, like this:
/solutions/:slug
If I visit the page directly in the browser, it loads correctly!
But if I click the nuxt-link in my NavBar component, I get the following error in the console, and the page does not load:
vue.runtime.esm.js?2b0e:619
[Vue warn]: Failed to mount component: template or render function not defined.
found in
---> <Anonymous>
<Nuxt>
<Layouts/default.vue> at layouts/default.vue
<Root>
I have a default layout file that looks like this:
layouts/default.vue
<template>
<div>
<NavBar />
<Nuxt />
</div>
</template>
<script>
import NavBar from "~/components/Layout/NavBar"
export default {
components: {
NavBar,
},
}
</script>
My navbar contains the following nuxt-link:
components/Layout/NavBar.vue
<template>
<b-navbar wrapper-class="container" fixed-top>
<template slot="end">
<nuxt-link
:to="{
name: 'solutions-slug',
params: { slug: 'performance' },
}"
class="navbar-item"
target="self"
>
<span class="icon is-medium">
<i class="ic ic--larger ic-b1-graph-bars-chart-rise" />
</span>
<span class="label">
Performance
</span>
</nuxt-link>
</template>
</b-navbar>
</template>
I have a page, defined by the slug param:
pages/solutions/_slug.vue
<template>
<div class="solution">
This is my solution page.
</div>
</template>
I am trying to understand why clicking the nuxt-link fails to load the page, even though I see the URL change in the browser correctly.
Thanks
After version v2.13, Nuxt can auto-import your components when used in your templates.
check the nuxt.config.js if components attribute is true then you don't need to import your component on the .vue files.
in your layouts/default.vue remove script tag ;-)
<template>
<div>
<NavBar />
<Nuxt />
</div>
</template>
If you need to categorize your components by folder, do the following.
goto nuxt.config.js and change your components attribute
export default {
components: {
dirs: [
'~/components',
{
path : '~/components/site/',
prefix: 'Site'
},
{
path : '~/components/admin',
prefix: 'Admin'
},
{
path : '~/components/admin/sub',
prefix: 'AdminSub'
}
]
}
}
for example, we have these components :
components
| site
- header
| admin
- header
- footer
| sub
- header
- footer
when we need to call components just separate prefixes and component names with a dash or write camelcase.
in your layouts/default.vue remove script tag ;-)
<template>
<div>
<!-- with dash -->
<site-header></site-header>
<admin-header></admin-header>
<admin-sub-header></admin-sub-header>
<!-- cammel -->
<SiteHeader></SiteHeader>
<AdminHeader></AdminHeader>
<AdminSubHeader></AdminSubHeader>
</div>
</template>
Attention: For Root Components /components/nav.vue, We Must Use CammelCase <Nav/> and if we call this component like this <nav/> it doesn't work.
Probably the problem is not related to anything described above.
First, check if your configuration is correct. I see you are using 'nuxtjs/content' module, so you are probably using Contentful as well. In the past, I have encountered a similar situation ('Failed to mount component: template or render function not defined' issue) due to incorrect installation of the 'dotenv' module that I used to store variables.
In my case, the application did not load variables from the .env file. As a consequence, they went to the Contentful client unidentified and caused the js error. For some reason, this error did not always appear in the console. Instead, the above-mentioned Warn appeared.
Make sure you have the 'dotenv' module correctly installed (if you use it). I remember that in my case it was necessary to install 'nuxtjs/dotenv' instead of the usual dotenv.
Let me know if this is the case. Good luck

Amplify Vue amplify-authenticator component not displaying

I have been using an <amplify-authenticator> component on the Home.vue view of a web application, via the HelloWorld.vue component. This was working as expected, with signedIn state monitored using store.js and Vuex.
I have now switched to routing to Home.vue or Login.vue depending on sign-in state.
However, when a signed out user correctly routes to Login.vue, the page displays as expected, minus the <amplify-authenticator> component. It is clear from the Styles that this is importing (amazonOrange is listed as a color), but for some reason the sign-in interface is no longer displaying correctly.
Login.vue
<template>
<div class="login">
<div>
<vue-headful title="Login"/>
</div>
<div>
<p>
<router-link to="/">Login</router-link> |
<router-link to="/about">About</router-link>
</p>
</div>
<div>
<p><img alt="Vue logo" src="../assets/logo.png" /></p>
</div>
<div>
<amplify-authenticator></amplify-authenticator>
</div>
</div>
</template>
<script>
import { Auth } from "aws-amplify";
import { AmplifyEventBus } from "aws-amplify-vue";
export default {
name: "Login",
props: {
msg: String
}
};
</script>
If I change amplify-authenticator to amplify-sign-in then I see a sign in interface, but this needs additional scripting to implement.
I'd like to know specifically why the Authenticator component isn't displaying, since I cannot see an obvious bug in my code.
Solved. If the user is signed in to Amazon Cognito, then the <amplify-authenticator> will not display. The problem was with a bug in the check on signedIn state, which was redirecting the user to the wrong page.

vue.js Mount component to app root

I have a modal.vue component as follows:
<template>
<transition name="modal-transition">
<div class="modal-body" v-if="displayed">
<div class="modal-overlay" #click="displayed = false"></div>
<div class="modal-content">
<slot/>
</div>
</div>
</transition>
</template>
How do I mount this component to the applications root element rather than in place?
For crude inaccurate example:
<body>
<div id="app">
<div class="header"></div>
<div class="nav"></div>
<div class="stage">
<div class="sub-nav"></div>
<div class="content">
<modal :display.sync="display">MY MODAL</modal> <-- Don't mount here...
</div>
</div>
<-- Mount here instead...
</div>
</body>
The current issue is that my sites header and navigation is layered on top of my modal and it's darkened full screen overlay instead of layered behind the modal overlay.
Update for Vue 3
There is now a built in feature called teleport which allows mounting parts of your component template to any DOM element.
The example from the OP would look like something like this
<!-- MyModal.vue -->
<template>
<transition name="modal-transition">
<div class="modal-body" v-if="displayed">
<div class="modal-overlay" #click="displayed = false"></div>
<div class="modal-content">
<slot/>
</div>
</div>
</transition>
</template>
<!-- SomeDeeplyNestedComponent.vue -->
<template>
<teleport to="#app">
<!-- Can still receive props from parent -->
<MyModal :my-prop="foo">
<!-- slot content -->
</MyModal>
</teleport>
</template>
Vue 2
Move the elements own self to the element of applications root may be achieved in two ways, Using a portal as a preferred solution or using an append.
Using a Portal (Preferred Method)
PortalVue is a set of two components that allow you to render a
component's template (or a part of it) anywhere in the document - even
outside the part controlled by your Vue App!
https://portal-vue.linusb.org/
Using an Append (Not best practice)
If adding a portal library is too heavy, using an append is allowed but lightly discouraged officially in the VUE docs.
Typically this particular mount position will satisfy a z-index overlay for your own modal or dialog popup that you require to render over the top of the entire app. You can always substitute this.$root.$el in this example for a different element target using standard getElementBy or querySelector functions.
Here the element is being moved not destroyed and re-added, all reactive functionality will remain in tact.
<script>
export default {
name: 'modal',
...
mounted: function() {
this.$root.$el.append(this.$el);
},
destroyed: function() {
this.$el.parentNode.removeChild(this.$el);
}
}
</script>
On mounted the element is moved inside of where the top level VUE app instance is mounted.
On destroyed removes the placeholder DOM comment for the migrated component from the new parent to prevent orphaned duplication each time the component remounts it's self.
VUE officially states not to destroy an element outside of VUE so this is not to be confused with that statement, here the component has already been destroyed.
This DOM comment duplication will typically happen when for example switching views with vue-router as this mechanism mounts and dismounts all components in a router view each time vue-router view state changes.
This behaviour is a bug cause by vue-router, the object is destroyed properly by VUE render manager but an index reference remains by mistake, using a portal package resolves this issue.
Here is the result: