Vuex store/navigation guard doesn't work first time

Viewed 470

I have a navigation guard on a user's dashboard which requires a user to exist to proceed.

export default new Router({
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/dashboard',
      name: 'dashboard',
      component: Dashboard,
      beforeEnter: (to, from, next) => {
        const user = JSON.parse(store.state.authenticatedUser)
        if(user) {
          console.log("Route accepted.")
          next()
        } else {
          console.log("Route rejected.")
          next('/')
        }
      }
    }
  ]
})

On my login form, I dispatch to obtainToken which returns the token and associated user.

<template>
  <v-container>
    <h1 class="display-2">Login</h1>
    <v-form 
      ref="form" 
      v-model="valid" 
      lazy-validation
    >
      <v-col cols="12" md="12">
        <v-row align="center">
          <v-text-field 
            v-model="email" 
            :rules=emailRules 
            label="Email" 
            required 
            dense
          ></v-text-field>
        </v-row>
        <v-row align="center">
          <v-text-field 
            v-model="password"
            label="Password"
            required
            dense
            type="password"
          ></v-text-field>
        </v-row>
      </v-col>
      <v-btn
        @click="login"
      >Login</v-btn>
    </v-form>
  </v-container>
</template>

<script>
export default {
  data: () => ({
    valid: true,
    password: '',
    email: '',
    emailRules: [
      v => !!v || 'Email is required.',
      v => /.+@.+\..+/.test(v) || 'You have entered an invalid email.',
    ],
  }),
  methods: {
    login() {
      this.$store.dispatch('obtainToken', { email: this.email, password: this.password })
      this.$router.push('dashboard')
    }
  }
}
</script>
  actions: {
    logout() {
      this.commit('revokeUser')
      this.commit('revokeToken')
    },
    obtainToken(random, payload) {
      const data = {
        email: payload.email,
        password: payload.password
      }

      api.post(this.state.endpoints.obtainToken, data)
      .then((response => {
        this.commit('updateSessionUser', response.data.user)
        this.commit('updateToken', response.data.token)
      }))
      .catch((e) => {
        console.log(e)
      })
    },

When clicking submit, the user is obtained, but the route is rejected because at the time a user doesn't exist. I have to click submit a second time which will allow the user to access the dashboard route.

enter image description here

So what I can gather from the output is that the $router.push is occurring as obtainToken is occurring so they race. Logging in a second time there is already a user present, so it proceeds. What am I doing wrong here?


I have as well tried to make it so router.push() is called as resolution to the promise as well.

login() {
      this.$store.dispatch('obtainToken', { email: this.email, password: this.password })
      .then(() =>
           this.$router.push('dashboard')
      )
}
2 Answers

The issue appears to be in the login method.

login() {
      this.$store.dispatch('obtainToken', { email: this.email, password: this.password })
      this.$router.push('dashboard')
}

Will be running both calls simultaneously. Attempting to navigate the user before the result of the obtain token call.

login() {
      this.$store.dispatch('obtainToken', { email: this.email, password: this.password })
      .then(() =>
           this.$router.push('dashboard')
      )
}

Once the token exists, it can be fetched from state when navigating to the dashboard.

Calling dispatch is async.

When changing the router, it is undefined yet.

So you need to call dispatch in mounted() before to move another page.

mounted() {
  this.$store.dispatch('obtainToken', { email: this.email, password: this.password })
},
methods: {
  login() {
    this.$router.push('dashboard')
  }
}

Or

methods: {
  login() {
    this.$store.dispatch('obtainToken', { email: this.email, password: this.password }).then(() => this.$router.push('dashboard'))
  }
}

EDIT: You need to return promise in obtainToken like below;

obtainToken(random, payload) {
  ...
  return api.post(this.state.endpoints.obtainToken, data) // just add `return`
  ...
}
Related