Vue render a component two times - vue.js

When I logged in the app and select edit some item, they make a $router.push to the edit view, the problem is that they render the component two times, I figured this out by doing a console.log on mounted(){}.. However, if I reload the page and click edit other time they make the render correctly, only one time.
This is the relevant code:
//listItemsView script
editItem(item) {
this.$router.push({ name: 'editPolicy', params:{policyTest: item}})
},
//editItemView script
export default {
props:{
policyTest:{
type: Object,
required: true,
}
mounted(){
console.log(this.policyTest);
console.log('entra');
},
}
//router script
{
path: '/editPolicy/',
name: 'editPolicy',
component: () => import('../views/policies/editPolicy.vue'),
props: true,
meta:{requireAuth:true}
}
router.beforeEach((to, from, next) => {
const user = auth.currentUser;
if(user !== null){
user.getIdTokenResult(true)
.then(function ({
claims
}) {
if (to.name === 'NewClient' && !claims.permissions.includes('Agregar Cliente')) {
next({name: 'notFoundPage'});
}else{
//In this case the router execute this next()
next()
}
})
} else {
if (to.matched.some(record => record.meta.requireAuth)) {
next({name: 'SignIn'});
} else {
next()
}
}
})
//html
<td class="text-left">
<v-icon small class="mr-2" #click="editItem(item)">fas fa-edit</v-icon>
</td>

i solved the problem, I'm use firebase authentication and in the main.js i'm detecting the user state change and for error i was creating two vue instances, one when start up the App and other when the user attemp to make their logging.. solved, thanks

Related

Vue JS - Find the router history -1

Is it possible with Vue2 to see where...
this.$router.go(-1)
Will take you?
We have a use case to redirect the user to last page they were on after they signed in, unless that is the forgot your password page. In that case we would want to send them back to the home page.
What is the best way to do this?
Vue 2 with Vue Router 3
You could use beforeRouteEnter navigation guard to track the previous route (stored in from):
export default {
beforeRouteEnter(to, from, next) {
next(vm => {
vm._backRoute = from.path
})
},
methods: {
goBack() {
if (this._backRoute === '/reset-password') {
this.$router.push({ name: 'Home' })
} else {
this.$router.go(-1)
}
}
}
}
demo 1
Vue 3 with Vue Router 4
You could read window.history.state.back to see what the back-state would be beforehand:
export default {
methods: {
goBack() {
if (window.history.state.back === '/reset-password') {
this.$router.push({ name: 'Home' })
} else {
this.$router.go(-1)
}
}
}
}
demo 2

Redirect after logged in

guys. Can someone help me to fix this problem with redirect?
I have this router file:
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!store.state.auth.is_authenticated) {
next({
path: '/login',
query: {
redirect: to.fullPath
}
})
} else {
next()
}
} else if (to.matched.some(record => record.meta.requiresGuest)) {
if (store.state.auth.is_authenticated) {
next({
path: '/',
query: {
redirect: to.fullPath
}
})
} else {
next()
}
} else {
next()
}
})
Current auth status is stored in vuex state. When I`m logging in status is changing but nothing is happening with page, only Header component updates.
<script>
import { mapGetters, mapActions } from "vuex";
export default {
name: "Login",
data: () => {
return {
email: "",
password: ""
};
},
computed: mapGetters(["currentUser"]),
methods: {
...mapActions(["loginUser"]),
login: function(event) {
event.preventDefault();
this.loginUser({ email: this.email, password: this.password });
}
},
updated() {
if (this.currentUser.is_authenticated) {
this.$router.go({ path: this.$router.path });
}
}
};
</script>
Please modify your code like this and check
helper.js
export function initialize(store, router)
{ router.beforeEach((to,from,next)=>{
const requiresAuth= to.matched.some(record=>record.meta.requiresAuth)
const currentUser = store.state.auth.user;
if(requiresAuth && !currentUser){ next('/login');}
else if(to.path=='/login' && currentUser){ next('/')}
//if user already authenticated and user tries to open login page
//redirect to / path , it can be modified as per config
else { next()}
})
}
Add this in your main.js
import {initialize} from './helper';
initialize(store, router);
In you login page updated part not required as router.beforeEach code in helper.js will be able to take care of that.
methods: {
...mapActions(["loginUser"]),
login: function(event) {
event.preventDefault();
this.loginUser({ email: this.email, password: this.password })
.then(()=>{ this.$router.replace({name:'dashboard'})
//forward to this path after login
}).catch(()=>{console.log('login fail') });
}
},
If store.state.auth.is_authenticated is true means that you logged in you must do after that flag go true something like this this.$router.go(); if you make this with no argument thats means that you will go in exact same page that you are (login page) but then it will triggered by your guards and it will redirect you to panel page or so

Detect vuex state change to execute a method inside a nuxt layout

I am trying to show vuetify snackbar alert, once I completed a form submission inside a page or vue component. I use vuex store to manage alert type and message.
my-nuxt-app/store/alerts.js
export const state = () => ({
message: '',
type: ''
});
export const getters = {
hasAlert(state) {
return state.message !== '';
},
alertMessage(state) {
return state.message;
},
alertType(state) {
return state.type;
}
};
export const mutations = {
SET_ALERT(state, payload) {
state.type = payload.type;
state.message = payload.message;
}
};
export const actions = {
setAlert({commit}, payload) {
commit('SET_ALERT', payload);
},
clearAlert({commit}) {
commit('SET_ALERT', {});
}
};
And I created a nuxt plugin to access getters globally in my application.
my-nuxt-app/plugins/alert.js
import Vue from 'vue';
import {mapGetters} from 'vuex';
const Alert = {
install(Vue, options) {
Vue.mixin({
computed: {
...mapGetters({
hasAlert: 'alerts/hasAlert',
alertType: 'alerts/alertType',
alertMessage: 'alerts/alertMessage'
})
}
});
}
};
Vue.use(Alert);
Inside my AccountForm component submit method, I am dispatching my alert information to store like below.
my-nuxt-app/components/form/AccountForm.vue
...
methods: {
async submit () {
try {
await this.$axios.patch("/settings/profile", this.form);
this.$store.dispatch('alerts/setAlert', {
type: 'success',
message: 'You have successfully updated your information.'
});
} catch (e) {
}
}
},
...
}
...
And this AccountForm.vue component is a child component of profile.vue page which is obviously inside the pages folder of my project. And also I have extended the dashboard.vue layout to this profile.vue page and to the most of the pages inside my pages directory as a common layout. Hence, I added the snackbar component into dashboard layout to show a alert message whenever required.
my-nuxt-app/layouts/dashboard.vue
<template>
...
<v-snackbar
:timeout="snackbar.timeout"
:color="snackbar.color"
:top="snackbar.y === 'top'"
:bottom="snackbar.y === 'bottom'"
:right="snackbar.x === 'right'"
:left="snackbar.x === 'left'"
:multi-line="snackbar.mode === 'multi-line'"
:vertical="snackbar.mode === 'vertical'"
v-model="snackbar.show"
>
{{ snackbar.text }}
<v-btn flat icon dark #click.native="snackbar.show = false">
<v-icon>close</v-icon>
</v-btn>
</v-snackbar>
...
</template>
<script>
...
data: () => ({
snackbar: {
show: false,
y: 'top',
x: null,
mode: '',
timeout: 6000,
color: '',
text: ''
},
}),
computed: {
availableAlert: function () {
return this.hasAlert;
}
},
watch: {
availableAlert: function(alert) {
if(alert) {
this.showAlert(this.alertType, this.alertMessage);
this.$store.dispatch('alerts/clearAlert');
}
}
},
methods: {
showAlert(type, message) {
this.snackbar.show = true;
this.snackbar.color = type;
this.snackbar.text = message;
}
}
</script>
I am getting the alert message for the first time submission of the form and after that I have to reload the page and then submit to get the alert. Please enlighten me a way to detect the vuex state change and trigger showAlert method inside the dashboard.vue accordingly.
It's most likely the way you're checking hasAlert
Your clearAlert passes an empty object, your setAlert is trying to assign properties of that empty object, while your hasAlert is checking if it's an empty string.
If you change your clearAlert to:
clearAlert({commit}) {
commit('SET_ALERT', { message: '', type: '' });
}
That should fix your issue.

Anchor in Nuxt component not working on same page the anchor is included on

In my Footer Component I have this to link to the owners bio on the about page
<nuxt-link :to="{path: '/about', hash: 'alex'}">Alex</nuxt-link>
In the about/index.vue file I have the anchor
<hr class="my-5" id="alex" />
<h2 style>
Alex
<br />
<span style="font-size: 16px; font-weight: bold;">Co-Founder and Partner</span>
</h2>
On all pages this works when you click the link in the footer. It does not work if you are on the about page and click the footer link.
What can I do to make this also work on the about page?
Update Nuxt Link as below
<nuxt-link :to="{path: '/about', hash: '#alex'}">Alex</nuxt-link>
++ Updated
Need to add scroll behavior in nuxt.config.js as below
router: {
scrollBehavior: async function(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
}
const findEl = async (hash, x = 0) => {
return (
document.querySelector(hash) ||
new Promise(resolve => {
if (x > 50) {
return resolve(document.querySelector("#app"));
}
setTimeout(() => {
resolve(findEl(hash, ++x || 1));
}, 100);
})
);
};
if (to.hash) {
let el = await findEl(to.hash);
if ("scrollBehavior" in document.documentElement.style) {
return window.scrollTo({ top: el.offsetTop, behavior: "smooth" });
} else {
return window.scrollTo(0, el.offsetTop);
}
}
return { x: 0, y: 0 };
}
},
Codesandbox Link
You can use vue-scrollto package also and if you are using Vuetify with Nuxtjs than there is $vuetify.goTo available.
Just wanted to add, if people are stuck, you can add a file called router.scrollBehavior.js in your nuxt project:
https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-router#scrollbehavior
Unfortunately, there are issues with it firing before the render - you can use nuxt/vue tick if you correctly import - but this still seems to work ( for anchors and for saved positions ):
export default async function (to, from, savedPosition) {
if (savedPosition) {
console.log("SAVED POSITION");
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
selector: savedPosition
});
}, 600);
});
// not working consistently due to render
//return savedPosition;
}
else if (to.hash) {
console.log("HASH");
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
selector: to.hash
});
}, 600);
});
}
else {
console.log("NORMAL");
return { x: 0, y: 0 };
}}
I am using Nuxt 3 and having the same problem, I solved the problem using this with thoughts of helping Nuxt 3 devs with same problem
Anchor tag with NuxtLink
<NuxtLink :to="{path: '/about', hash: '#support'}">Support</NuxtLink>
On the Nuxt page <script setup>
// Make sure you are in the client
// browser to access `document` variable
if (process.client) {
// get current route
const {currentRoute : route} = useRouter();
// make sure that hash is defined
if (route.value?.hash) {
// set hash
const hash = route.value?.hash
// use onMounted hook to run the code inside
// after the page is mounted to the DOM
onMounted(() => {
// get target element
let el = document.querySelector(hash)
// make sure that the element exists then scroll to that element
if(el) {
if ('scrollBehavior' in document.documentElement.style) {
window.scrollTo({ top: el.getBoundingClientRect().top+window.scrollY, behavior: 'smooth' })
} else {
window.scrollTo(0, el.getBoundingClientRect().top+window.scrollY)
}
}
})
}
}
If your are on Vue 3, you will have to create a file here app/router.options.ts with the following code inside
import type { RouterConfig } from '#nuxt/schema'
// https://router.vuejs.org/api/#routeroptions
export default <RouterConfig>{
scrollBehavior(to, from, savedPosition) {
return { el: to.hash }
}
}
Your nuxt link should look like this
<NuxtLink :to="{ path: '/', hash: '#projects' }">Projects</NuxtLink>
Checkout more more

Using vue.js and Auth0 how can i skip the login page if the user's already authenticated?

DESCRIPTION
I have a pretty standard SPA built with vue.js where I'm using Auth0 to handle the authentication part by following the official example. The app flow is as follows:
Register in the Initial.vue via Auth0 lock -> Callback is called -> User's redirected to /home
Everything in the above flow works fine but here's the problem:
PROBLEM
Once the user's registered and in the /home I want him to be able to access all the other routes (e.g. /doctors) if authenticated and if not he should be prompter to login again. According to the above link this is handled in the router.beforeEach function.
My problem appears when accessing the / login page (Initialvue). When the user's already registered and trying to access that route I want him to get redirected to the /home and skip the login page. I've tried implementing this with a beforeEnter route but auth.isAuthenticated fails due to the tokenExpiry being null (even-though user's authenticated!
CODE
My AuthService.js:
import auth0 from 'auth0-js';
import EventEmitter from 'events';
import authConfig from '../config/auth_config.json';
const webAuth = new auth0.WebAuth({
domain: authConfig.domain,
redirectUri: `${window.location.origin}/callback`,
clientID: authConfig.clientId,
responseType: 'id_token',
scope: 'openid profile email'
});
const localStorageKey = 'loggedIn';
const loginEvent = 'loginEvent';
class AuthService extends EventEmitter {
idToken = null;
profile = null;
tokenExpiry = null;
// Starts the user login flow
login(customState) {
webAuth.authorize({
appState: customState
});
}
// Handles the callback request from Auth0
handleAuthentication() {
return new Promise((resolve, reject) => {
webAuth.parseHash((err, authResult) => {
if (err) {
reject(err);
} else {
this.localLogin(authResult);
resolve(authResult.idToken);
}
});
});
}
localLogin(authResult) {
// console.log(authResult); TODO-me: Handle this
this.idToken = authResult.idToken;
this.profile = authResult.idTokenPayload;
// Convert the JWT expiry time from seconds to milliseconds
this.tokenExpiry = new Date(this.profile.exp * 1000);
localStorage.setItem(localStorageKey, 'true');
this.emit(loginEvent, {
loggedIn: true,
profile: authResult.idTokenPayload,
state: authResult.appState || {}
});
}
renewTokens() {
return new Promise((resolve, reject) => {
if (localStorage.getItem(localStorageKey) !== "true") {
return reject("Not logged in");
}``;
webAuth.checkSession({}, (err, authResult) => {
if (err) {
reject(err);
} else {
this.localLogin(authResult);
resolve(authResult);
}
});
});
}
logOut() {
localStorage.removeItem(localStorageKey);
this.idToken = null;
this.tokenExpiry = null;
this.profile = null;
webAuth.logout({
returnTo: window.location.origin
});
this.emit(loginEvent, { loggedIn: false });
}
isAuthenticated() {
console.log('In tokenExp is:');
console.log(this.tokenExpiry); //THIS returns null when /home -> /
return (
Date.now() < this.tokenExpiry &&
localStorage.getItem(localStorageKey) === 'true'
);
}
}
export default new AuthService();
My Initial.vue:
<template>
<v-container
app
fluid
>
<v-parallax
src="https://cdn.vuetifyjs.com/images/backgrounds/vbanner.jpg"
height="1000"
>
<v-layout
row
wrap
>
<!-- LOGIN-->
<v-toolbar
flat
light
dense
color="transparent"
>
<v-spacer></v-spacer>
<v-toolbar-items>
<v-btn
medium
color="lime lighten-2"
#click="login"
class="font-weight-bold title text-uppercase"
>
Login
</v-btn>
</v-toolbar-items>
</v-toolbar>
<v-layout
align-center
column
>
<h1 class="display-2 font-weight-thin mb-3 text-uppercase lime--text lighten-2" >Pulse</h1>
<h4 class="subheading">A digital intelligent insurance built for you!</h4>
</v-layout>
</v-layout>
</v-parallax>
</v-container>
</template>
<script>
import VContainer from "vuetify/lib/components/VGrid/VContainer";
import VFlex from "vuetify/lib/components/VGrid/VFlex";
import VLayout from "vuetify/lib/components/VGrid/VLayout";
import VBtn from "vuetify/lib/components/VBtn/VBtn";
import VToolbar from "vuetify/lib/components/VToolbar/VToolbar";
import VParallax from "vuetify/lib/components/VParallax/VParallax";
export default {
name: "Initial",
components: {
VContainer,
VLayout,
VFlex,
VBtn,
VToolbar,
VParallax
},
data() {
return {
isAuthenticated: false
};
},
async created() {
try {
await this.$auth.renewTokens();
} catch (e) {
// console.log(e);
}
},
methods: {
login() {
this.$auth.login();
},
// logout() {
// this.$auth.logOut();
// },
handleLoginEvent(data) {
this.isAuthenticated = data.loggedIn;
this.profile = data.profile;
}
}
}
</script>
<style scoped>
</style>
My Callback.vue:
<template>
<div>
<p>Loading...</p>
</div>
</template>
<script>
export default {
methods: {
handleLoginEvent(data) {
console.log('State.target is:');
console.log(data.state.target);
//If user has just signed up redirect to complete-signup form
if ((data.profile['<AUTH_DOMAIN>'].justSignedUp) && (data.state.target===undefined)){
// this.$router.push(data.state.target || "/complete-signup");
this.$router.push('/complete-signup');
}else {
// this.$router.push('/home');
this.$router.push(data.state.target);
}
}
},
created() {
this.$auth.handleAuthentication();
}
}
</script>
<style scoped>
</style>
My router.js:
import Vue from 'vue';
import Router from 'vue-router';
import auth from '../auth/AuthService';
import Callback from '../components/Callback';
Vue.use(Router)
// export default new Router({
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
// {
// path: '/',
// name: 'login',
// component: () => import('#/views/Login')
// },
{
path: '/',
name: 'initial',
component: () => import('#/views/Initial'),
// meta: {isAuth: true},
beforeEnter: ((to, from, next) => {
// if (to.matched.some(record => record.meta.isAuth)) {
console.log(auth.isAuthenticated()); //THIS is false for the above scenario
if (auth.isAuthenticated()) {
next({
path: '/home',
query: {redirect: to.fullPath}
})
} else {
next()
}
// }
})
},
{
path: '/callback',
name: 'callback',
component: Callback
},
{
path: '/home',
name: 'home',
component: () => import('#/views/Home')
},
{
path: '/doctors',
name: 'doctors',
component: () => import('#/views/Doctors')
},
{
path: '/complete-signup',
name: 'complete-signup',
component: () => import('#/views/CompleteSignup')
},
]
});
// Add a `beforeEach` handler to each route
router.beforeEach((to, from, next) => {
if (to.path === "/" || to.path === "/callback" || auth.isAuthenticated()) {
return next();
}
// Specify the current path as the customState parameter, meaning it
// will be returned to the application after auth
console.log('OUT beforeach if');
auth.login({ target: to.path });
});
The 'CompleteSignupis my signup form after registering where the user's is filling out a form and then posting it viaaxiosand then redirected to/home`:
//Form data before
methods: {
this.$store.dispatch(REGISTER,registerFormData)
.then(() => this.$router.push('/home'));
}
I'm also using vuetify and my main App.vue component is:
<template>
<v-app
style= "background: #E0EAFC; /* fallback for old browsers */
background: -webkit-linear-gradient(to left, #CFDEF3, #E0EAFC); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to left, #CFDEF3, #E0EAFC); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
"
>
<v-content>
<router-view></router-view>
</v-content>
</v-app>
</template>
<script>
export default {
name: 'App',
components: {
}
};
</script>
<style>
</style>
You can simplify the problem by making the default behaviour the one where your user is logged in and then protect the respective routes with the route guard.
1) point your / to /home
2) create separate route for login/"intial"
3) use the beforeEach hook to ensure a user is authenticated and if not redirect him to your Initial.vue (or trigger auth.login() directly)
...
{
path: '/',
redirect: 'home'
},
{
path: '/initial',
...
...
}
...
router.beforeEach((to, from, next) => {
if(to.name == 'callback' || auth.isAuthenticated()) {
next()
} else { // trigger auth0 login or redirect to your Initial.vue
auth.login()
// next({ path: '/initial' })
}
})
I figured it out. The problem lies in the fact that the this.tokenExpiry, this.idToken, this.profile values are being assigned a value within the callback view which comes after the login one and in addition to that these values are tied to the specific Vue prototype instance that I'd defined in a mixin. So when navigating back to the initial page this is undefined since it's not associated to a specific Vue instance.
I had been in the same situation (with React) and I will try to explain how to solve it.
First, in Authentication, it is necessary to store the token on the client side (localStorage / cookie can be used)
You need a SecureComponent, this component will only check if the stored token exists
FetchHandler this component handles eachProtected Application
and will inspect the response code, and if it is 401 (for example) it will redirect a User to the Authentication Component. This step can be added to SecureComponent as an additional layer.