Triggering a route only after dispatch and commit completed in VueJS - vue.js

I do have a form submit which takes email and password then pass them into an action in store called userSignIn
SignIn.vue :
onSubmit () {
if (this.$refs.form.validate()) {
const user = {
email: this.email,
password: this.password
}
this.$store.dispatch('userSignIn', user)
.then(() => {
this.$router.push('/')
}).catch(err => {
console.log(err)
})
}
}
Within store, I do have a userSignIn action like this
store.js actions:
userSignIn ({commit, getters}, payload) {
getters.Api.post(`user/signin`, {
email: payload.email,
password: payload.password
}).then(res => {
commit('userSignIn', res.data.token)
}).catch(err => {
console.log(err)
})
}
The routing(this.$router.push('/')) should only be done after userSignIn commit(commit('userSignIn', res.data.token)). But what actually happening routing triggers before commit, which results and error because user token is not set yet.
How to trigger something(in this case this.$router.push('/')) only after completion of dispatch and commit within it?

Returning the promise did the trick.
userSignIn ({commit, getters}, payload) {
return getters.Api.post(`user/signin`, {
......
})

Related

How to wait for state change before executing next action in Vuex

I'm trying to login and get user name at the same time. Here is how it works:
First action login gets access token and updates state.accessToken
Using state.accessToken I need to get user data at the same time (fetchUser) when login is pressed, however when fetchUser is executed state.accessToken is still null as actions are asynchronous. What is the best practice to wait for state change before executing the next action? I tried to look up for examples, but solutions found are not applicable for my case.
store.js
const store = new Vuex.Store({
state: {
accessToken: null,
user: null
},
mutations: {
authUser (state, userData) {
state.accessToken = userData.accessToken
},
storeUser (state, user) {
state.user = user
}
}
actions: {
login({commit}, authData) {
axios.post("http://example.com/token/create/", {
email: authData.email,
password: authData.password
})
.then(res => {
commit('authUser', {
accessToken: res.data.access
})
})
},
fetchUser({commit, state}) {
axios.get("http://example.com/api/auth/v1/me/", {
headers: {Authorization: "Bearer " + state.accessToken}
})
.then(res => {
commit('storeUser', res.data.user)
})
}
}
getters: {
user (state) {
return state.user
},
isAuthenticated(state) {
return state.accessToken !== null
}
}
})
login.vue
<template>
<form #submit.prevent="submitForm">
<div v-if="!auth" class="row">
<input class="col" placeholder="Email" v-model="formInfo.email" type="text"></input>
<input class="col" placeholder="Password" v-model="formInfo.password" type="password"></input>
<button class="col" type="submit" label="Log In"></button>
</div>
<div v-else class="row">
Hello {{ firstname }}
</div>
</form>
</template>
<script>
export default {
data() {
return {
formInfo: {
email: '',
password: ''
}
};
},
methods: {
submitForm() {
this.$store.dispatch('login', {email: this.formInfo.email, password: this.formInfo.password})
this.$store.dispatch('fetchUser')
}
},
computed: {
auth() {
return this.$store.getters.isAuthenticated
},
firstname() {
return this.$store.getters.user.firstname
}
}
}
};
</script>
have you tried adding await?
async submitForm() {
await this.$store.dispatch('login', {email: this.formInfo.email, password: this.formInfo.password});
await this.$store.dispatch('fetchUser');
}
it will wait for the login to finish before fetching the user
also maybe add async in your actions methods:
async login({commit}, authData) {...}
async fetchUser({commit, state}) {...}
You can dispatch an action from another action.
actions: {
login({commit, dispatch}, authData) {
axios.post("http://example.com/token/create/", {
email: authData.email,
password: authData.password
})
.then(res => {
const accessToken = res.data.access;
dispatch('fetchUser', accessToken);
commit('authUser', { accessToken })
})
},
fetchUser({commit, state}, accessToken) {
axios.get("http://example.com/api/auth/v1/me/", {
headers: {Authorization: "Bearer " + accessToken}
})
.then(res => {
commit('storeUser', res.data.user)
})
}
}
Evan's async/await solution should work.
If there are multiple pages/components that can issue actions in any order, and you need to not proceed with one action before another is complete, you could use the strategy of storing promises as state. You can then access the promise any other action depends on the completion of and await it before proceeding.
So in login, you would use async/await syntax and an extra mutation call:
const promise = axios.post("http://example.com/token/create/", {
email: authData.email,
password: authData.password
});
commit('loginPromise', promise);
const result = await promise;
You would need to add the above mutation.
Then in fetchUser, before issuing the API call:
await state.loginPromise
That would guarantee that the promise has completed. You will have to handle error scenarios of course.

Vuex promise reject returns undefined

I want the promise reject to return the error to my method but the response is empty inside my methods then() function, how can i get the error response to be returned to my method for further use or even inside the catch function.
My vuex action
//authAction
login({ commit }, payload) {
new Promise((resolve, reject) => {
user.login(payload.user.email, payload.user.password)
.then(response => {
const user = response.data.user;
// If there's user data in response
if (user) {
const payload = [user]
commit('AUTH_SUCCESS', payload, { root: true })
resolve(response)
} else {
reject({ message: "Sorry, cant login right now" })
}
})
.catch(error => {
console.log(error.response.status)
reject(error)
})
})
}
My method
// Login method
login() {
if (!this.checkLogin()) return;
this.$vs.loading();
const payload = {
checkbox_remember_me: this.checkbox_remember_me,
user: {
email: this.email,
password: this.password
}
};
this.$store
.dispatch("auth/login", payload)
.then(res => {
this.$vs.loading.close();
console.log(res);
})
.catch(error => {
this.$vs.loading.close();
this.$vs.notify({
title: "Error",
text: error.message,
});
});
}
What am i missing?
Thanks in advance!
My solution is to 1. dispatch an action whenever an error is thrown which updates state 2. watch state change in view and do something with it

Redux automatically return initalState after some action call

I am using react-redux and the reducer automatically return the inital state on call of an action, for example(given code) when i call the fetchPost action or the createPost action my Auth reducer automatically return the inital state.
why is it happening so i am using redux-thunk and backend with feathersJS.
export const login = (payload) => dispatch => {
AsyncStorage.getItem('feathers-jwt').then(r => {
dispatch({ type: 'CHECK_AUTHORIZATION', payload: r })
}).catch(() => {
client.authenticate({
strategy: 'local',
email: payload.email,
password: payload.password
}).then(r => {;
dispatch({ type: 'LOGIN_REQUEST', payload: r })
}).catch(e => {
dispatch({ type: 'LOGIN_ERROR', payload: e.message })
})
})
}
export const checkAuthorization = () => dispatch => {
AsyncStorage.getItem('feathers-jwt').then(r => {
dispatch({ type: 'CHECK_AUTHORIZATION', payload: r });
return client.passport.verifyJWT(r);
}).then(payload => {
dispatch({ type: 'JWT_PAYLOAD', payload: payload })
return client.service('users').get(payload.userId);
}).then(user => {
client.set('user', user);
client.get('user').then(user => {
dispatch({ type: 'PROFILE', payload: user })
})
})
.catch(() => {
dispatch({ type: 'AUTHORIZATION_FAILED' })
})
}
export const logout = (payload) => dispatch => {
client.logout().then(r => dispatch({ type: 'LOG_OUT', payload: r }))
}
import actions from './actions';
import AsyncStorage from '#react-native-community/async-storage';
const initalState = {
users: [],
isAuthenticated: false,
accessToken: null,
profile: null,
isVendor: false,
isConsumer: false,
errorMessage: null,
jwtPayload: null
};
export default (state = initalState, action) => {
switch (action.type) {
case 'CHECK_AUTHORIZATION':
return Object.assign({}, state, {
accessToken: action.payload,
isAuthenticated: true
})
case 'AUTHORIZATION_FAILED':
return Object.assign({}, state, {
isAuthenticated: initalState.isAuthenticated
})
case 'LOGIN_REQUEST':
return (
Object.assign({}, state, {
accessToken: action.payload.accessToken,
isAuthenticated: true,
}));
case 'JWT_PAYLOAD':
return Object.assign({}, state, {
jwtPayload: action.payload
})
case 'PROFILE':
return Object.assign({}, state, {
profile: action.payload
})
case 'LOGIN_ERROR':
return Object.assign({}, state, {
errorMessage: action.payload.message
})
case 'LOG_OUT':
return Object.assign({}, state, {
isAuthenticated: initalState.isAuthenticated,
accessToken: null
})
default:
return initalState;
}
}
try setting your default to
default:
return state;
instead of
default:
return initalState;
Sometimes, we may put call an action upon form submit. Then page gets reload and entire redux gets initialized from beginning.
For an example,
<form>
<button type="submit" onClick={()=>dispatch(someAction)}>Foo</button>
</form>
Above code will call that action and redux will once again initialize.

Redux async actioncreator not recognizing then

I need to use .then() on a redux action, what is wrong in the following action?
export const userLogin = (username, password) => {
return dispatch => {
axios.post(`${TT_API_BASE}/Access/Login`, { username: username, password: password, applicationId: 2 }, {
headers: {
'Content-Type': 'application/json;charset=utf-8',
'Authorization': 'Basic ' + auth,
}
})
.then(response => {
dispatch({
type: LOGIN,
payload: response.data
})
})
.catch(err => {
console.log(err)
dispatch({
type: LOGIN_FAILED
})
})
}
}
It is then called in a component like this
handlePress() {
this.props.userLogin(this.state.username, this.state.password)
.then(() => {
this.props.navigation.navigate('SelectInstance')
})
}
Which displays the errormessage that then is not defined. What am I doing wrong?
When you do dispatch(someThunkActionCreator()), the return value of dispatch is whatever your thunk function returns. So, you can only do dispatch().then() if the thunk function returns a promise.
Your thunk is making an AJAX call, but not actually returning a promise, so it actually returns undefined. Putting a return statement in front of axios.post() will return that promise and fix the problem.
Solved by doing this:
export const userLogin = (username, password) => {
return async dispatch => {
const onSuccess = data => {
dispatch({
type: LOGIN,
payload: data
})
}
const onError = err => {
dispatch({
type: LOGIN_FAILED
})
}
try {
const req = await axios.post(`${TT_API_BASE}/Access/Login`, { username: username, password: password, applicationId: 2 }, {
headers: {
'Content-Type': 'application/json;charset=utf-8',
'Authorization': 'Basic ' + auth,
}
})
return onSuccess(req.data)
}
catch (err) {
return onError(err)
}
}
}

How to update view inside axios promise and after store dispatch?

I have a simple vue app where I'm trying to add simple authentication. Inside my login.vue, I use axios to authenticate the user via ajax and store the token returned by the api in the store then redirect to a new page (ex: dashboard.vue).
The problem is that the token is saved but the view is not updated, can't call router.push() ...
Any ideas why isn't it working ? Thanks
Login.vue
methods: {
authenticate () {
var dataLogin = {
email: this.login,
password: this.password
}
var headers = { headers: { 'Content-type': 'application/json', 'Accept': 'application/json' } }
axios.post(config.apiUrl, dataLogin, headers)
.then(response => {
this.$store.dispatch('login', response.data).then(() => {
// if there is no error go to home page
if (!this.$store.getters.error) {
this.$router.push('/')
}
})
})
.catch(error => {
this.errorMessage = error.response.data.message
this.authError = true
})
}
}
The store function just save the token with localStorage
const actions = {
login (context, data) {
context.commit('authenticate', data)
}
}
const mutations = {
authenticate (state, data) {
localStorage.setItem('user-access_token', data.access_token)
}
}
You are calling a then() handler when you dispatch the action.
But your action does not return a promise.
So return a promise in your action as follows:
const actions = {
login (context, data) {
return new Promise((resolve, reject) => {
context.commit('authenticate', data)
resolve()
})
}
}
Also chain your promises for better readability
axios.post(config.apiUrl, dataLogin, headers)
.then(response => {
return this.$store.dispatch('login', response.data)
}).then(() => {
// if there is no error go to home page
if (!this.$store.getters.error) {
this.$router.push('/')
}
})
.catch(error => {
this.errorMessage = error.response.data.message
this.authError = true
})