vue-router not updating component outside of router-view - vue.js

I have my root Vue component setup like this:
<div>
<main-nav></main-nav>
<router-view></router-view>
</div>
So, main-nav is a component outside of the router-view. I'm having trouble with the main-nav updating itself properly when data changes or when navigating from route to route. Here's a simplified version of main-nav:
<template>
<li v-show="loggedIn">
<a #click="logout">Logout</a>
</li>
</template>
<script>
import { isLoggedIn, logout } from '#/tools/Auth'
export default {
computed: {
loggedIn: {
cache: false,
get() {
return isLoggedIn()
}
}
},
methods: {
logout() {
logout().then(() => {
this.$router.push({ name: 'Login' })
// this is necessary or the nav isn't updated after logout
this.$forceUpdate()
})
}
}
}
</script>
First I had to set cache: false on the loggedIn property or it wouldn't update at all after logging out. I also had to add a $forceUpdate() after logout in order for the component to refresh itself. Now I'm having the same problem after login - the component just doesn't refresh. I can see in the vue dev tools that loggedIn is true, but it won't show the logout link until I refresh the page.
It's not a race condition, there's nothing async going on at the moment. I'm just setting and deleting a cookie right now to get this working.
Anyone have an idea why I'm having these issues? I'm fairly familiar with vue-router from other projects and I've read the documentation but maybe I missed something?

Related

How does Vue lifecycle works? and use it for skeleton and loading

this question my look silly but i got stuck in this!
i'm using Vuetify skeleton and created a data isLoading like the codes bellow. on page refresh every thing is fine but on route change (go back and forward in my pages) it's not working.
in my codes, when page is refreshed btn becomes disabled and mycomponent show skeleton using the same isLoading in its file. but when i go forward and back to it, my btn is loaded, not disabled, mycomponent load after a time without showing skeleton!
what's the problem! i guest it's about using the lifecycle!
<template>
<div>
<v-btn :disabled="isLoading">Button</v-btn>
<mycomponent />
</div>
</template>
<script>
import mycomponent from '~/components/mycomponent'
export default {
components:{
'mycomponent': mycomponent
},
data(){
return{
isLoading: true
}
},
created(){
this.isLoading = true
},
mounted(){
this.isLoading = false
}
}
</script>
mycomponent:
<template>
<v-skeleton-loader
:loading="isLoading"
type="button"
width="100%"
>
<v-btn>Button</v-btn>
</v-skeleton-loader>
</template>
<script>
export default {
data(){
return{
isLoading: true
}
},
created(){
this.isLoading = true
},
mounted(){
this.isLoading = false
}
}
</script>
So The Problem: It only works when i land on page for first time or refresh browser. by going forward and back to this page neither disable button nor skeleton on component works.
Update
I'm on NuxtJs v2.13
The created hook is called when the vue instance is created and the mounted hook is called when the vue instance has been mounted in the DOM. These hooks are called when a component is routed to for the first time or when the page is refreshed. That explains why it only works when you land on the page for the first time or refresh the browser.
When a component has been mounted, pressing the back button on the browser won't call the created and mounted hook.
To solve your problem, you can watch the $route object, by doing
App.vue
watch: {
'$route' () {
// this will be called any time the route changes
this.isLoading = true // you can think of a way to make isLoading false
}
},
For more on lifecycle hooks, check out this article.

Refresh required to detect authentication state using nuxt auth module

My app is unable to detect the state change that occurs when a user logs in without completely refreshing the page. Upon refreshing everything displays correctly. I am using Nuxt and its included auth module documented here - https://auth.nuxtjs.org/.
Here is the v-if statement that is unable to detect the state change:
<template v-if="$auth.$state.loggedIn">
<nuxt-link
to="/profile"
>
Hello, {{ $auth.$state.user.name }}
</nuxt-link>
</template>
<template v-else>
<nuxt-link
to="/logIn"
>
Sign In
</nuxt-link>
</template>
Here is the login method in my login page.
methods: {
async onLogin() {
try{
this.$auth.loginWith("local", {
data: {
email: this.email,
password: this.password
}
});
this.$router.push("/");
}catch(err){
console.log(err);
}
}
}
I tried fetching the state via a computed property but got the same result. I can see the vuex store data change to indicate I am correctly logged in/out in the 'Application' tab in Chrome Dev Tools but the Vue Dev seems to constantly indicate I'm logged in.. Not sure if its just buggy though..
I also encounter the same problem in reverse when logging out. Here's the method:
async onLogout() {
try{
await this.$auth.logout();
}catch(err){
console.log(err);
}
}
I am happy to provide further details.
In store/index.js add this :
export const getters = {
isAuthenticated(state) {
return state.auth.loggedIn
},
loggedInUser(state) {
return state.auth.user
},
};
In the pages you are suppose to be authenticated
use middle ware auth as : middleware: 'auth'
use import { mapGetters } from 'vuex'
in computed add ...mapGetters(['isAuthenticated', 'loggedInUser']),
you can use loggedInUser to get your user details or check if isAuthenticated
and the logout would work as expected as long as your are importing the map getters in the computed
Sometimes Vue's reactivity system falls short and you just need to manually trigger a re-render and the simplest way to do so is by wrapping your function logic in setTimeout()
setTimeout(async () => {
await this.$auth.logout();
}, 0);

Reload navbar component on every this.$router.push() call

I developing a login/registration system in my Vue.js app. I want the items in navbar to be updated when I call this.$router.push('/').
App.vue:
<template>
<div id="app">
<Navbar></Navbar>
<router-view></router-view>
<Footer></Footer>
</div>
</template>
Navbar component:
export default {
name: "Navbar",
data: function() {
return {
isLoggedIn: false,
currentUser: null
}
},
methods: {
getAuthInfo: function() {
this.isLoggedIn = this.auth.isLoggedIn();
if (this.isLoggedIn) {
this.currentUser = this.auth.currentUser();
}
}
},
mounted: function() {
this.getAuthInfo();
},
updated: function() {
this.getAuthInfo();
}
}
Here is how I redirect to another page:
const self = this;
this.axios
.post('/login', formData)
.then(function(data) {
self.auth.saveToken(data.data.token);
self.$router.push('/');
})
.catch(function(error) {
console.log(error);
self.errorMessage = 'Error!';
});
SUMMARY: The problem is that isLoggedIn and currentUser in Navbar don't get updated when I call self.$router.push('/');. This means that functions mounted and updated don't get called. They are updated only after I manually refresh the page.
I solved the problem with adding :key="$route.fullPath" to Navbar component:
<template>
<div id="app">
<Navbar :key="$route.fullPath"></Navbar>
<router-view></router-view>
<Footer></Footer>
</div>
</template>
Check this out from the docs:
beforeRouteUpdate (to, from, next) {
// called when the route that renders this component has changed,
// but this component is reused in the new route.
// For example, for a route with dynamic params `/foo/:id`, when we
// navigate between `/foo/1` and `/foo/2`, the same `Foo` component instance
// will be reused, and this hook will be called when that happens.
// has access to `this` component instance.
},
I expect your Navbar component is reused across routes so its mounted and updated are not called. Try using beforeRouteUpdate if you want to do some processing on route change.

Navigating vuejs SPA via routes that share component does not refresh component data as expected

I have a couple routes in my vuejs SPA that I have set up using vue-router:
/create/feedback
/edit/feedback/66a0660662674061b84e8ea2fface0e4
The component for each route is the same form with a bit of smarts to change form values based on the absence or present of the ID in the route (feedbackID, in my example).
I notice that when I click from the edit route to the create route, the data in my form does not clear.
Below is the gist of my route file
import FeedbackFormView from './components/FeedbackForm.vue'
// Routes
const routes = [
{
path: '/create/feedback',
component: FeedbackFormView,
name: 'FeedbackCreate',
meta: {
description: 'Create Feedback',
}
},
{
path: '/edit/feedback/:feedbackId',
component: FeedbackFormView,
name: 'FeedbackEdit',
meta: {
description: 'Edit Feedback Form'
},
props: true
}
]
export default routes
Below is the gist of my component
<template lang="html">
<div>
<form>
<input v-model="model.someProperty">
</form>
</div>
</template>
<script>
export default {
data() => ({model: {someProperty:''}}),
props: ['feedbackId'],
created() => {
if (!this.$props['feedbackId']) {
return;
}
// otherwise do ajax call and populate model
// ... details omitted
}
}
</script>
However, if I modify my component as follows, everything works as expected
<template lang="html">
<div>
<form>
<input v-model="model.someProperty">
</form>
</div>
</template>
<script>
export default {
data() => ({model: {someProperty:''}}),
props: ['feedbackId'],
created() => {
if (!this.$props['feedbackId']) {
return;
}
// otherwise do ajax call and populate model
// ... details omitted
},
watch: {
'$route' (to, from) {
if (to.path === '/create/feedback') {
this.model = {}
}
}
}
}
</script>
Why is this? Why do I need watch?
I would have though that changing routes would be sufficient as the purpose of routing is to mimic the semantic behavior of page navigation
You have same component for different routes, when you go to edit route from the create route component is already created and mounted so the state of the component doesn't clear up.
Your component can listen to route changes using $router provided by vue-router every time the route changes the watcher is called.
For those who come this later, the following answer addresses the issue I was facing:
Vue-Router: view returning to login page after page refresh

Hiding Element Based on Route Path or Params in Vue

I'm trying to hide the main app navigation bar based on if the route is on a given path.
In my App.vue component, in the created() method. I do check to see if the route is x || y, if either of those are true, I set my Vuex state of show to false. If it is any other route besides those two, I set show = true.
Then in my template I do this
<template>
<div id="app">
<navigation v-show="show"></navigation>
<router-view></router-view>
</div>
</template>
I'm noticing in Vuex tools that my mutations aren't even registering so I'm not sure why that is. Do they need to be actions instead? Here is my full code.
<template>
<div id="app">
<navigation v-show="show"></navigation>
<router-view></router-view>
</div>
</template>
<script>
import Navigation from './components/Navigation/Navigation'
import { firebaseAuth } from './firebase/constants'
import store from './store/index'
export default {
name: 'app',
components: {
Navigation
},
computed: {
show () {
return store.state.navigation.show
}
},
created() {
// Checks for a user and dispatches an action changing isAuthed state to true.
firebaseAuth.onAuthStateChanged(user => {
console.log(store.state.authentication);
console.log(user);
store.dispatch('checkUser', user);
});
// Check if given route is true, if it is then hide Nav.
if (this.$route.path === "/dashboard/products" || this.$route.path === "/dashboard/settings") {
store.commit('hideNav');
} else if (this.$route.path !== "/dashboard/products" || this.$route.path !== "/dashboard/settings") {
store.commit('showNav');
}
}
};
</script>
This may not be working as created is called only once after the instance is created. but when routes changes, it will not be called, so not triggering the mutations you are expecting to trigger on route change, instead of this, you can put a watch on route, so on each route change, you can check whether to show your Nav Bar or not, like following;
Working fiddle: http://jsfiddle.net/ofr8d85p/
watch: {
$route: function() {
// Check if given route is true, if it is then hide Nav.
if (this.$route.path === "/user/foo/posts") {
store.commit('SHOWNAV');
} else {
store.commit('HIDENAV');
}
}
},