10
votes

After a user has successfully logged into my app via a login form. I want to check if they have a profile. If they do I want to send them to News page. If they don't have one set, I want it to redirect them to the Settings page. How would I do this using Vuex?

I guess there are a few options:

1. Components responsible for the redirects? (doesn't feel right)

Login.vue

handleFormSubmit () {
this.$store.dispatch('loginFormSubmit', formData)
    .then(() => {
      // This feels a bit gross
      this.$store.dispatch('getUserProfile')
        .then(() => this.$router.push('/news'))
        .catch(() => this.$router.push('/settings'))
    })
    .catch(() => {
      this.loginError = true
    })
}

2. Would I keep the responsibility in the actions?

user-actions.js

export const getUserProfile = ({commit}) => {
  commit(types.USER_PROFILE_REQUEST)
  const options = {
    url: `${API_URL}/profile`,
    method: 'GET'
  }

  return request(options)
    .then((profileSettings) => {
      // Would I change the router to News here??

      commit(types.USER_PROFILE_SUCCESS, profileSettings)
      return Promise.resolve(profileSettings)
    })
    .catch((error) => {
      // Would I change the router to Settings here??

      commit(types.UI_MENU_HIDE)
      return Promise.reject(error)
    })
}

export const loginFormSubmit = ({dispatch, commit}, { email, password }) => {
  console.log(email, password)
  commit(types.USER_LOGIN_REQUEST)
  const options = {
    url: `${API_URL}/token`
  }
  return request(options)
    .then((response) => {
      dispatch('getUserProfile')
      commit(types.USER_LOGIN_SUCCESS, response.token)
      return Promise.resolve(response.token)
    })
    .catch((error) => {
      commit(types.USER_LOGIN_FAILURE, error)
      return Promise.reject(error)
    })
}

3. Maybe it should be kept in the router with guards?

Login.vue

handleFormSubmit () {
this.$store.dispatch('loginFormSubmit', formData)
    .then(() => this.$router.push('/news'))
    .catch(() => {
      this.loginError = true
    })
}

Then in my router:

const routes = [
      {
        path: 'news',
        alias: '',
        component: NewsView,
        name: 'News',
        meta: { description: 'News feed page' },
        beforeEnter: (to, from, next) => {
          store.dispatch('getUserProfile')
-          .then((resp) => {
-            // Profile set
-            next()
-          })
-          .catch(() => {
-            // No profile set
-            next({ name: 'settings' })
-          })
        }
      },
]

4. Listen out for store change, then change route

  computed: {
    isAuthenticated () {
      return this.$store.getters.isAuthenticated
    }
  },

  watch: {
    isAuthenticated () {
      this.$router.push('/calendar')
    }
  },
  methods: {
    handleSubmit (formData) {
      // Updates isAuthenticated on success
      this.$store.dispatch('requestAuthToken', formData)
    }
  }

Maybe number three or four is best? It feels the cleanest but would love to know what you guys think :D

Thanks for taking the time in advance!

3
Did you found out the best approach?Marina
I really like #4 but #3 would probably work too. I just used #4 this to solve this problem for myself, actually. Thanks for posting this. Sorry you didn't get a response. I marked this original question as a favorite.Oak

3 Answers

0
votes

I think you want a version of your idea #2. Why not something like this:

  1. You call handleFormSubmit() in Login.vue which dispatches loginFormSubmit() action. Maybe enable a loading state in Login.vue at this point for UX.
  2. If the user can login, you call getUserProfile(), else show error in Login.vue
  3. getUserProfile() fetches from the API
  4. You test response from the API either within your action or pass response to a seperate mutation which does that.
  5. If the user has profile data, update state and router.push('/pathToNews'), else router.push('/pathToSettings')

Here is simplified psuedo-code to illustrate the concept:

// store

  state: {
    userProfile: {}
  },

  mutations: {
    setUserProfile (state, payload) {
      state.userProfile = payload
    }
  },

  actions: {
    getUserProfile() {
      let self = this
      axios.get('${API_URL}/token')
      .then(function (response) {
        // test response against what is expected for a populated profile
        if (response.data is a populated user profile) {
          self.commit('setUserProfile', response.data)
          self.router.push('/news')
        } else {
          self.router.push('/settings')
        }
      })
      .catch(function (error) {
        console.error(error);
        // handle your error
      })
      .finally(function () {
        // always executed
      })
    }
  }
0
votes

I personally use a combination of 2 solutions for redirects/handling errors:

  1. Use an axios interceptor for the most clear cut status codes:

    import axios from 'axios'
    
    axios.interceptors.response.use(response => response, error => {
        const { status } = error.response
        const errorMessage = error.response.data.message
    
        if (status === Constants.STATUS_CODE_ERROR_NOT_FOUND) {
            router.push('/404')
           return
        } else if (status === Constants.STATUS_CODE_ERROR_UNAUTHENTICATED){
          ...
        }
        ...
     }
    
  2. Make the check in the action and return in the component for redirecting or other necessary operations.

On another note, please consider using async/await instead of then/catch.

-1
votes

my flow:

  1. component (this.$store.dispatch('getProfile'...)
  2. action - server (const response = await fetch ...) commit a response to the mutation
  3. mutation save info to the store
  4. store (the only place contain all information)
  5. component (reactively fetch data from this.$store.state.user.hasProfile and make a decision what to do )

summary: you send request {login, password}, then receive response from server something like {hasProfile: true/false}

    if(hasProfile) {
      // router to
    } else  {
      // router to
    }