Issue with cancelling axios .then logic with interceptors and cancel token - javascript

I set up an axios response interceptor for my react app. It works fine for catching most errors but one thing i am having trouble with is if the response is a 401 aka the user is not authed, the interceptor sends the user back to the login page. Now this works but the logic inside the .then from the original request still runs. This causes a type error as in the .then logic i am setting a state with the response data. Here is my current attempt at implementing a axios cancel token that is not working. See the code below. What am i missing here? What is the best way to achieve this with out having to add If/Else logic to every axios request to check if "data" is there or is the response is a 401, 200 ...?
AxiosInterceptor.js
...
export default withRouter({
useSetupInterceptors: (history) => {
axios.interceptors.response.use(response => {
return response;
}, error => {
try {
if (error.response.status === 401) {
history.push("/login");
Swal.fire({
title: '401 - Authorization Failed',
text: '',
icon: 'warning',
showCancelButton: false,
confirmButtonText: 'Close',
})
throw new axios.Cancel('Operation canceled');
}
return Promise.reject(error);
} catch (error) {
console.log(error)
}
});
},
});
UserPage.js
...
function userPage() {
var [pageData, setPageData] = useState('');
var classes = useStyles();
useEffect(() => {
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const loadData = () => {
try {
axios.post('/api/getUserData', { cancelToken: source.token })
.catch(function (error) {
source.cancel();
})
.then(res => {
const data = res.data;
setPageData(data);
})
} catch (error) {
if (axios.isCancel(error)) {
console.log('Op Cancel')
} else {
throw error;
}
}
};
loadData();
return () => {
source.cancel();
};
}, []);
return (
...
);
}
...
The error i get:
Unhandled Rejection (TypeError): Cannot read property 'data' of undefined
PROGRESS UPDATE:
I added some logic to my back-end that if the login is successful,
i pass the expiration time of the JWT token back to my front end.
Then push that expiration epoch to my redux store.
On every request, in my 'AxiosInterceptor.js' file below, before returning a config back, i validate the exp value set in redux.
Now this works fine on initial login, but once the token has expired and you receive the popup from 'Swal.fire' and click 'return' it does two things:
calls logOut action and returns all values to initial state. (This works fine. I validated with redux-devtools-extension)
Now i can log back in. Everything starts to load fine but then i get the 'Swal.fire' dialog to return back to login page. When logging the user.exp and date.now to console i see some strange behavior(see comments):
// from redux-logger
action SET_EXP # 20:05:42.721
redux-logger.js:1 prev state {user: {…}, _persist: {…}}
redux-logger.js:1 action {type: "SET_EXP", payload: 1585267561036}
USEREXP 1585267561036 // this is the new EXP time set in redux, received from back end on login
AxiosInterceptors.js:17 Current Status = 1585267561036 false // first two axios calls on main page validate and indicate not expired
AxiosInterceptors.js:17 Current Status = 1585267561036 false
AxiosInterceptors.js:17 Current Status = 1585267495132 true // this is the value of the previos exp value that was set
AxiosInterceptors.js:17 Current Status = 1585267495132 true
AxiosInterceptors.js:17 Current Status = 1585267352424 true // this is the value that was set two login times ago
AxiosInterceptors.js:17 Current Status = 1585267352424 true
How is this possible? I verified with redux-devtools that once i am
returned back to the login page, it is indeed empty. It appears the value in > redux-store is being rolled back to old values? I am using chrome Version
74.0.3729.131 (Official Build) (64-bit). I have tried with incognito mode and clearing cache and cookies.
New AxiosInterceptor.js ...
export default withRouter({
useSetupInterceptors: (history) => {
let user = useSelector(state => state.user)
axios.interceptors.request.use(config => {
const { onLogo } = useLogout(history);
console.log("Current Status = ", user.exp, Date.now() > user.exp)
if (Date.now() > user.exp) {
Swal.fire({
title: '401 - Auth Failed',
text: '',
icon: 'warning',
showCancelButton: false,
confirmButtonText: 'Return',
}).then((result) => {
onLogo();
})
return {
...config,
cancelToken: new CancelToken((cancel) => cancel('Cancel')) // Add cancel token to config to cancel request if redux-store expire value is exceeded
};
} else {
return config;
}
}, error => { console.log(error)});
axios.interceptors.response.use(response => {
return response;
}, error => {
try {
if (axios.isCancel(error)) { // check if canceled
return new Promise(() => {}); // return new promise to stop axios from proceeding to the .then
}
if (error.response.status === 401) {
history.push("/login");
Swal.fire({
title: '401 - Auth Failed',
text: '',
icon: 'warning',
showCancelButton: false,
confirmButtonText: 'Close',
})
throw new axios.Cancel('Operation canceled');
}
return Promise.reject(error);
} catch (error) {
console.log(error)
}
});
},
});
function useLogo(history) {
const dispatch = useDispatch()
return {
onLogo() {
dispatch(allActs.userActs.logOut())
history.push("/login");
},
}
}

I tracked down the issue to the hook "useSelector" within react-redux. It seems this is some how returning cached data, after it already returned correct data. I am using version 7.2 at his time but i confirmed it also on v7.1. I have not tested on any other versions. I solved this by pulling the data from redux-persist Storage(localStorage) in the getExpire() function below. Not the most elegant solution but my application is now working as it should be.
export default withRouter({
useSetupInterceptors: (history) => {
const { onLogout } = useLogout(history);
const CancelToken = axios.CancelToken;
const { onExp } = useExp();
axios.interceptors.request.use((config) => {
const testexp = onExp();
if (testexp) {
Swal.fire({
title: '401 - Authorization Failed',
text: '',
icon: 'warning',
showCancelButton: false,
confirmButtonText: 'Return',
}).then((result) => {
onLogout();
})
return {
...config,
cancelToken: new CancelToken((cancel) => cancel('Cancel repeated request'))
};
} else {
return config;
}
}, error => { console.log(error) });
axios.interceptors.response.use(response => {
return response;
}, error => {
try {
if (axios.isCancel(error)) {
return new Promise(() => { });
}
return Promise.reject(error);
} catch (error) {
console.log(error)
}
});
},
});
function getExpire () {
var localStore = localStorage.getItem("persist:root")
if (localStore) {
let store = JSON.parse(localStore)
return JSON.parse(store.exp)
}
return 0
}
function useExp() {
// const currentExp = useSelector(state => state.exp)
return {
onExp() {
if (Date.now() > getExpire().exp) {
return true
} else { return false }
},
}
}
function useLogout(history) {
const dispatch = useDispatch()
return {
onLogout() {
dispatch(allActions.expAction.setLogout())
history.push("/login");
},
}
}

Related

Return Response When First request failed And Try In Second Request

I try to explain the problem.in App.js I have Function getUser .when call this function.in first request get 401 error . For this reason in axios.interceptors.response I receive error 401.At this time, I receive a token and repeat my request again.And it is done successfully.But not return response in Function getUser.
I have hook for authentication and send request.
import React from "react";
import axios from "axios";
const API_URL = "http://127.0.0.1:4000/api/";
function useJWT() {
axios.interceptors.request.use(
(request) => {
request.headers.common["Accept"] = "application/json";
console.log("request Send ");
return request;
},
(error) => {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(response) => {
console.log("answer = ", response);
return response;
},
(error) => {
if (error?.response?.status) {
switch (error.response.status) {
case 401:
refreshToken().then((responseTwo) => {
return
sendPostRequest(
error.response.config.url
.split("/")
.findLast((item) => true)
.toString(),
error.response.config.data
);
});
break;
case 500:
// Actions for Error 500
throw error;
default:
console.error("from hook interceptor => ", error);
throw error;
}
} else {
// Occurs for axios error.message = 'Network Error'
throw error;
}
}
);
const refreshToken = () => {
const token = localStorage.getItem("refresh");
return axios
.post(API_URL + "token", {
token,
})
.then((response) => {
if (response.data.access) {
localStorage.setItem("access", response.data.access);
}
if (response.data.refresh) {
localStorage.setItem("refresh", response.data.refresh);
}
return response.data;
});
};
function login(email, password) {
return axios
.post(API_URL + "login", {
email,
password,
})
.then((response) => {
if (response.data.access) {
localStorage.setItem("access", response.data.access);
}
if (response.data.refresh) {
localStorage.setItem("refresh", response.data.refresh);
}
return response.data;
});
}
const sendPostRequest = (url, data) => {
console.log(300);
const token = localStorage.getItem("access");
axios.defaults.headers.common["jwt"] = token;
return axios.post(API_URL + url, {
data,
});
};
const logout = () => {
const token = localStorage.getItem("refresh");
return axios
.delete(API_URL + "logout", {
token,
})
.then((response) => {
localStorage.removeItem("access");
localStorage.removeItem("refresh");
});
};
return {
login,
logout,
refreshToken,
sendPostRequest,
};
}
export default useJWT;
In App.js ,I want to repeat the same request again if a 401 error is issued when I read the user information.
The request is successfully repeated but does not return the value.
When first request fail response is return equals null . and in catch when receive 401 error i am send second request but not return response.
I send request below code .
const getUser = () => {
console.log(12);
return sendPostRequest("user");
};
useEffect(() => {
let token = localStorage.getItem("access");
console.log("token = ", token);
if (token != null) {
//Here I have done simulation for 401 error
localStorage.setItem("access", "");
getUser()
.then((response) => {
console.log("response 1= ", response);
})
.catch((exception) => {
console.log("exception = ", exception.toString());
})
.then((response) => {
console.log("response 2= ", response);
});
} else {
navigate("/login");
}
}, []);
Best regards.
I didn't fully understand what exactly you want to do here.
But if you are looking to retry when 401 happens, you could use axios-retry to do it for you.
I'll pass the basics, but you can look more into what this does.
// First you need to create an axios instance
const axiosClient = axios.create({
baseURL: 'API_URL',
// not needed
timeout: 30000
});
// Then you need to add this to the axiosRetry lib
axiosRetry(axiosClient, {
retries: 3,
// Doesn't need to be this, it can be a number in ms
retryDelay: axiosRetry.exponentialDelay,
retryCondition: (error) => {
// You could do this way or try to implement your own
return error.response.status > 400
// something like this works too.
// error.response.status === 401 || error.response.status >= 500;
}
});
Just like in your code, we need to use interceptors if you want to avoid breaking your page, otherwise you can use try catch to catch any errors that may happen in a request.
// It could be something like this, like I said, it's not really needed.
axiosClient.interceptors.response.use(
(success) => success,
(err) => err
);
And finally, you could use the axiosClient directly since it now has your API_URL, calling it like this axiosClient.post('/user').
More or less that's it, you should just debug this code and see what is causing the return value to be null.
I would change these then/catch to be an async/await function, it would be more readable making your debugging easier.
axios-retry example if you didn't understand my explanation.
I find anwser for this question.
When error 401 occurs then create new Promise
I Wrote this code.
case 401:
return new Promise((resolve, reject) => {
refreshToken().then((responseTwo) => {
resolve(
sendPostRequest(
error.response.config.url
.split("/")
.findLast((item) => true)
.toString(),
error.response.config.data
)
);
});
});

Vuex: Wait for websocket response before dispatching action

So this is the scenario / premises:
In order to populate a chat queue in real time I need to open a connection to a websocket, send a message and then set the data to a websocket store. This store will basically manage all the websocket state.
Before populating the chat queue there's two parameters I need: a shiftId coming from one http API request and a connectionId coming from the websocket. Using those two parameters I finally can subscribe to a third http API and start receiving messages to populate the chat queue.
The problem is that due to the async behaviour of the websocket (or that's what I think, please feel to correct me if I'm wrong) I always get an empty "connectionId" when trying to make the put to that "subscription" API. I have tried with async/await and promises but nothing seems to work. I'm pretty new to async/await and websockets with Vuex so pretty sure I'm doing something wrong.
This is the user vuex module where I do all the login/token operations and dispatch a "updateEventsSubscription" action from the shift vuex module. In order for the "updateEventsSubscription" action to work I need to get the response from the "processWebsocket" action (to get the connectionId parameter) and from the "startShift" action (to get the shiftId parameter) coming from the shifts vuex module:
import UserService from '#/services/UserService.js'
import TokenService from '#/services/TokenService.js'
import router from '#/router'
export const namespaced = true
export const state = {
accessToken: '',
errorMessage: '',
errorState: false,
userEmail: localStorage.getItem('userEmail'),
userPassword: localStorage.getItem('userPassword'),
}
export const mutations = {
SET_TOKEN(state, accessToken) {
state.accessToken = accessToken
TokenService.saveToken(accessToken)
},
SET_USER(state, authUserJson) {
state.userEmail = authUserJson.email
state.userPassword = authUserJson.password
localStorage.setItem('userPassword', authUserJson.password)
localStorage.setItem('userEmail', authUserJson.email)
},
SET_ERROR(state, error) {
state.errorState = true
state.errorMessage = error.data.error_description
},
CLOSE_NOTIFICATION(state, newErrorState) {
state.errorState = newErrorState
},
}
export const actions = {
signIn({ commit, dispatch, rootState }, authUserJson) {
return UserService.authUser(authUserJson)
.then((result) => {
commit('SET_USER', authUserJson)
commit('SET_TOKEN', result.data.access_token)
dispatch('token/decodeToken', result.data.access_token, {
root: true,
})
dispatch(
'shifts/updateEventsSubscription',
rootState.token.agentId,
{
root: true,
}
)
router.push('/support')
})
.catch((error) => {
console.log(error)
if (error.response.status === 400) {
commit('SET_TOKEN', null)
commit('SET_USER', {})
commit('SET_ERROR', error.response)
} else {
console.log(error.response)
}
})
},
signOut({ commit }) {
commit('SET_TOKEN', null)
commit('SET_USER', {})
localStorage.removeItem('userPassword')
localStorage.removeItem('userEmail')
TokenService.removeToken()
router.push('/')
},
closeNotification({ commit }, newErrorState) {
commit('CLOSE_NOTIFICATION', newErrorState)
},
}
export const getters = {
getToken: (state) => {
return state.accessToken
},
errorState: (state) => {
return state.errorState
},
errorMessage: (state) => {
return state.errorMessage
},
isAuthenticated: (state) => {
return state.accessToken
},
userEmail: (state) => {
return state.userEmail
},
userPassword: (state) => {
return state.userPassword
},
}
This is websocket store: I pass the connectionId to the state in order to be able to use it in another vuex action to subscribe for new chats:
export const namespaced = true
export const state = {
connected: false,
error: null,
connectionId: '',
statusCode: '',
incomingChatInfo: [],
remoteMessage: [],
messageType: '',
ws: null,
}
export const actions = {
processWebsocket({ commit }) {
const v = this
this.ws = new WebSocket('mywebsocket')
this.ws.onopen = function (event) {
commit('SET_CONNECTION', event.type)
v.ws.send('message')
}
this.ws.onmessage = function (event) {
commit('SET_REMOTE_DATA', event)
}
this.ws.onerror = function (event) {
console.log('webSocket: on error: ', event)
}
this.ws.onclose = function (event) {
console.log('webSocket: on close: ', event)
commit('SET_CONNECTION')
ws = null
setTimeout(startWebsocket, 5000)
}
},
}
export const mutations = {
SET_REMOTE_DATA(state, remoteData) {
const wsData = JSON.parse(remoteData.data)
if (wsData.connectionId) {
state.connectionId = wsData.connectionId
console.log(`Retrieving Connection ID ${state.connectionId}`)
} else {
console.log(`We got chats !!`)
state.messageType = wsData.type
state.incomingChatInfo = wsData.documents
}
},
SET_CONNECTION(state, message) {
if (message == 'open') {
state.connected = true
} else state.connected = false
},
SET_ERROR(state, error) {
state.error = error
},
}
And finally this is the shift store (where the problem is), as you can see I have a startShift action (everything works fine with it) and then the "updateEventsSubscription" where I'm trying to wait for the response from the "startShift" action and the "processWebsocket" action. Debugging the app I realize that everything works fine with the startShift action but the websocket action sends the response after the "updateEventsSubscription" needs it causing an error when I try to make a put to that API (because it needs the connectionId parameter coming from the state of the websocket).
import ShiftService from '#/services/ShiftService.js'
export const namespaced = true
export const state = {
connectionId: '',
shiftId: '',
agentShiftInfo: '{}',
}
export const actions = {
startShift({ commit }, agentId) {
return ShiftService.startShift(agentId)
.then((response) => {
if (response.status === 200) {
commit('START_SHIFT', response.data.aggregateId)
}
})
.catch((error) => {
console.log(error)
if (error.response.status === 401) {
console.log('Error in Response')
}
})
},
async updateEventsSubscription({ dispatch, commit, rootState }, agentId) {
await dispatch('startShift', agentId)
const shiftId = state.shiftId
await dispatch('websocket/processWebsocket', null, { root: true })
let agentShiftInfo = {
aggregateId: state.shiftId,
connectionId: rootState.websocket.connectionId,
}
console.log(agentShiftInfo)
return ShiftService.updateEventsSubscription(shiftId, agentShiftInfo)
.then((response) => {
commit('UPDATE_EVENTS_SUBSCRIPTION', response.data)
})
.catch((error) => {
if (error.response.status === 401) {
console.log('Error in Response')
}
})
},
}
export const mutations = {
START_SHIFT(state, shiftId) {
state.shiftId = shiftId
console.log(`Retrieving Shift ID: ${state.shiftId}`)
},
UPDATE_EVENTS_SUBSCRIPTION(state, agentShiftInfo) {
state.agentShiftInfo = agentShiftInfo
},
}
You should convert your WebSocket action into a promise that resolves when WebSocket is connected.:
export const actions = {
processWebsocket({ commit }) {
return new Promise(resolve=> {
const v = this
this.ws = new WebSocket('mywebsocket')
this.ws.onopen = function (event) {
commit('SET_CONNECTION', event.type)
v.ws.send('message')
resolve();
}
this.ws.onmessage = function (event) {
commit('SET_REMOTE_DATA', event)
}
this.ws.onerror = function (event) {
console.log('webSocket: on error: ', event)
}
this.ws.onclose = function (event) {
console.log('webSocket: on close: ', event)
commit('SET_CONNECTION')
ws = null
setTimeout(startWebsocket, 5000)
}
});
},
}
So I realized that I have to resolve the promise on the this.ws.message instead. By doing that all my data is populated accordingly, there's still sync issues (I can't feed the websocket state at the moment because due to its async behaviour the state is not there yet when other components try to use it via: rootGetters.websocket.incomingChats for example) but I guess that's part of another question. Here's the final version of the module action:
export const actions = {
processWebsocket({ commit }) {
return new Promise((resolve) => {
const v = this
this.ws = new WebSocket('wss://ws.rubiko.io')
this.ws.onopen = function (event) {
commit('SET_CONNECTION', event.type)
v.ws.send('message')
}
this.ws.onmessage = function (event) {
commit('SET_REMOTE_DATA', event)
resolve(event)
}
this.ws.onerror = function (event) {
console.log('webSocket: on error: ', event)
}
this.ws.onclose = function (event) {
console.log('webSocket: on close: ', event)
commit('SET_CONNECTION')
ws = null
setTimeout(startWebsocket, 5000)
}
})
},
}
Anyways, thanks #Eldar you were in the right path.

Having problem handling errors in react-redux white attempting to SignUp

Here i have my component code for SignIng Up user and check for Error. At first error is null.
let error = useSelector((state) => state.authReducer.error);
const checkErrorLoading = () => {
console.log("If error found"); //At first it gives null, but on backend there is error
toast.error(error);
console.log(loading, error);
};
const handleSubmit = async (e) => {
if (isSignup) {
dispatch(signup(form, history));
checkErrorLoading();
} else {
dispatch(signin(form, history));
checkErrorLoading();
}
};
Now at my singupForm, i provide wrong input or wrong data. The backend gives me error that is completely fine.
ISSUE => But when i click on Login button. At first attempt it does not provide any error message. After second attempt it works fine, but not at first attempt. At first attempt it gives me Error value NULL while there is still an error
Here is my action.
export const signup = (formData, history) => async (dispatch) => {
try {
const res = await api.signUp(formData);
dispatch({ type: authConstants.AUTH_REQUEST });
if (res.status === 200) {
const { data } = res;
console.log(data);
dispatch({
type: authConstants.AUTH_SUCCESS,
payload: data,
});
}
console.log(res.status);
history.push("/");
} catch (error) {
console.log(error.response);
dispatch({
type: authConstants.AUTH_FAILURE,
payload: error.response.data.error,
});
}
};
and than reducer.
const initialState = {
authData: null,
error: null,
loading: false,
};
const authReducer = (state = initialState, action) => {
switch (action.type) {
case authConstants.AUTH_REQUEST:
return { ...state, loading: true, error: null };
case authConstants.AUTH_SUCCESS:
localStorage.setItem("profile", JSON.stringify({ ...action?.payload }));
return { ...state, authData: action?.data, loading: false, error: null };
case authConstants.AUTH_FAILURE:
console.log(action.payload);
return { ...state, loading: false, error: action.payload };
}
You should use useEffect instead of local function (checkErrorLoading ) for such cases:
useEffect(() => {
console.log("If error found");
toast.error(error);
console.log(loading, error);
},[error]);
Currently what you doing is creating local function that closures error variable, which initially is null + state is updated asynchronously, so you cannot execute function right after dispatching (even if variable wouldn't be closured, you will not have fresh state there)

How to redirect after dispatching an action in React.js

I have implemented a signup view with backend validation, and I want to redirect to Login view after successfull signup.
Here is my dispatching method:
export const initSignup = (details) => {
return (dispatch) => {
axios.post("http://localhost:8080/auth/signup", {
email: details.email,
password: details.password
}).then((result) => {
console.log(result);
return dispatch(_signup(result));
})
.catch((err) => {
//use err.response to get our custom err response from backend
//since once we send 400+ response status it goes to catch block
console.log(err);
dispatch(_signupError(err.response));
})
}
}
const _signup = (result) => {
return {
type: SIGNUP,
result: result
}
}
const _signupError = (err) => {
return {
type: ERROR,
error: err
}
}
After disptach _signup I want to redirect to "/login".
Can someone help me here.
You need to use routes with paths and history prop
const { from } = { from: { pathname: "/myPath" } };
this.props.history.push(from);

How to throw an error in the outer function from an inner async function like setTimeout in JavaScript?

i want to sign in to in my react native app using firebase authentication. For example, to login with email and password i have the following function, which is basically a redux action:
export const userSignin = (email, password) =>
async dispatch => {
try {
dispatch({ type: 'auth_attempt_started' })
const user = await firebase.auth().signInWithEmailAndPassword(email.trim(), password)
saveUserDataToAsyncStorage(user)
if (!TASKS_SORT_BY && !TASKS_SORT_ORDER)
dispatch(getTasksSortData(user.user.uid))
if (!EMPLOYEES_SORT_BY && !EMPLOYEES_SORT_ORDER)
dispatch(getEmployeesSortData(user.user.uid))
if (!ACCOUNTS_SORT_BY && !ACCOUNTS_SORT_ORDER)
dispatch(getAccountsSortData(user.user.uid))
goToMain()
setTimeout(() => {
dispatch({
type: 'user_signedin',
payload: user
})
}, 100);
}
catch (err) {
dispatch({
type: 'auth_error',
payload: err.toString()
})
}
}
This function throw an error if something went wrong with the signin process.
The problem is when the internet connection is slow, the signin process take a long time before the success or the failure, sometimes around 30 seconds which is very bad for the user experience.
The question is:
How can i throw an error after sometimes maybe 10 seconds if the signin process does not complete?
Thankyou!
In general, it can be achieved with setTimeout.
componentDidMount() {
this.setTimeout( () => {
this.doThisAfterTenSeconds();
},10000);
}
doThisAfterTenSeconds() {
// Do something
}
In this particular scenario,
export const userSignin = (email, password) =>
async dispatch => {
try {
// Auth attempt has started
dispatch({ type: 'auth_attempt_started' })
// Lets add a 10 second timeout
let hasTimedOut = false
setTimeout(() => {
hasTimedOut = true
// Display message here or throw "Connection timed out"
}, 10000)
// Now lets await the authentication
const user = await firebase.auth().signInWithEmailAndPassword(email.trim(), password)
// This code should not be run if auth attempt timed out. If we throw an error the check should not be neccessary...
if (!hasTimedOut)
{
saveUserDataToAsyncStorage(user)
if (!TASKS_SORT_BY && !TASKS_SORT_ORDER)
dispatch(getTasksSortData(user.user.uid))
if (!EMPLOYEES_SORT_BY && !EMPLOYEES_SORT_ORDER)
dispatch(getEmployeesSortData(user.user.uid))
if (!ACCOUNTS_SORT_BY && !ACCOUNTS_SORT_ORDER)
dispatch(getAccountsSortData(user.user.uid))
goToMain()
setTimeout(() => {
dispatch({
type: 'user_signedin',
payload: user
})
}, 100);
}
}
catch (err) {
dispatch({
type: 'auth_error',
payload: err.toString()
})
}
}

Categories