I have been trying to refactor it , we have an async await function receiving credentials from the form component file which dispatches the credentials onSubmit
dispatch(submitLogin(credentials));
the submitLogin function is an exported async function with a try catch block
try {
//post login credentials
const res = await axios.post("seller/auth/login", credentials);
//extract token from data
const token = res.data.data.token;
//store token in local storage
localStorage.setItem("token", token);
//set token
setToken(token);
const decoded = {
name: res.data.data.name,
id: res.data.data.id,
};
const decodedJwt = jwt_decode(token);
localStorage.setItem("id", decoded.id);
localStorage.setItem("types", decodedJwt.types);
localStorage.setItem("sellerName", decoded.name);
const secondResponse = await axios.get("seller/auth/check-token",{
headers: {
"Authorization": `Bearer ${token}`
}
});
localStorage.setItem("isItValid", secondResponse.data.message);
dispatch({
type: GET_ERRORS,
payload: {},
});
dispatch({
type: SET_CURR_SELLER,
payload: decoded,
});
} catch (error) {
dispatch({
type: GET_ERRORS,
payload: error,
});
}
when I console.log(action) from reducers file it only logs GET_ERRORS
and the error message says
Invalid token specified: Cannot read properties of undefined (reading 'replace')
Is there a way to capture the error response from axios in a try/catch redux-saga?
I have the following axios logging function:
export const login = (username, password) => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const body = JSON.stringify({ username, password });
return axios
.post('http://127.0.0.1:8000/accounts-api/login', body, config)
.then(response => response.data)
.catch(error => error.response.data)
};
which returns
{user: {…}, token: "b5d36ec9086f7f2403a02c2e8d70695e85a48cad2d81e39c9b31fddaa759a79e"}
when the password and username is correct or
{non_field_errors: ["Incorrect Credentials"]}
when password or username is not correct.
The login function is called in the following redux-saga generator:
export function* signIn({ payload: { username, password } }) {
try {
const response = yield login(username, password)
let { user, token } = yield response
yield console.log(user)
yield console.log(token)
} catch(e) {
yield console.log(e)
}
}
Is there a way to capture the error response from axios ( in this case {non_field_errors: ["Incorrect Credentials"]} )in redux-saga catch?
Now with incorect user credential user and token are set to undefined and function is not moving on to redux-saga catch.
I'm developping an API consuming web front site.
The problem
All my API saga were like this :
export function* login(action) {
const requestURL = "./api/auth/login"; // Endpoint URL
// Select the token if needed : const token = yield select(makeSelectToken());
const options = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + btoa(JSON.stringify({ login: action.email, password: action.password })),
}
};
try {
// The request helper from react-boilerplate
const user = yield call(request, requestURL, options);
yield put(loginActions.loginSuccess(user.token);
yield put(push('/'));
} catch (err) {
yield put(loginActions.loginFailure(err.detailedMessage));
yield put(executeErrorHandler(err.code, err.detailedMessage, err.key)); // Error handling
}
}
And I had the same pattern with all my sagas :
Select the token if I need to call a private function in the start of the saga
const token = yield select(makeSelectToken());
Handle errors on the catch part
export const executeErrorHandler = (code, detailedMessage, key) => ({
type: HTTP_ERROR_HANDLER, status: code, detailedMessage, key
});
export function* errorHandler(action) {
switch (action.status) {
case 400:
yield put(addError(action.key, action.detailedMessage));
break;
case 401:
put(push('/login'));
break;
//other errors...
}
}
export default function* httpError() {
yield takeLatest(HTTP_ERROR_HANDLER, errorHandler);
}
The solution I came up with
Remove the token parts and error handling part and puth them inside the call helper :
export function* login(action) {
const url = `${apiUrl.public}/signin`;
const body = JSON.stringify({
email: action.email,
password: action.password,
});
try {
const user = yield call(postRequest, { url, body });
yield put(loginSuccess(user.token, action.email));
yield put(push('/'));
} catch (err) {
yield put(loginFailure());
}
}
// post request just call the default request with a "post" method
export function postRequest({ url, headers, body, auth = null }) {
return request(url, 'post', headers, body, auth);
}
export default function request(url, method, headers, body, auth = null) {
const options = { method, headers, body };
return fetch(url, addHeader(options, auth)) // add header will add the token if auth == true
.then(checkStatus)
.then(parseJSON)
.catch(handleError); // the error handler
}
function handleError(error) {
if (error.code === 401) {
put(push('/login')); // <-- Here this doesn't work
}
if (error.code == 400) {
displayToast(error);
}
}
function addHeader(options = {}, auth) {
const newOptions = { ...options };
if (!options.headers) {
newOptions.headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
...options.headers,
};
}
if (auth) {
const token = yield select(makeSelectToken()); // <-- here it doesn't work
newOptions.headers.Authorization = `Bearer ${auth}`;
}
return newOptions;
}
I know the solution is between generator functions, side effects, yield call / select but I tried so many things it didn't work. For example, if I wrap everything inside generator functions, the token load is executed after the code continues and call the API.
Your help would be appreciated.
You need to run any and all effects (e.g. yield select) from a generator function, so you'll need generators all the way down to the point in your call stack where you yield an effect. Given that I would try to push those calls as high as possible. I assume you may have getRequest, putRequest etc. in addition to postRequest so if you want to avoid duplicating the yield select you'll want to do it in request. I can't fully test your snippet but I believe this should work:
export function* postRequest({ url, headers, body, auth = null }) {
return yield call(request, url, 'post', headers, body, auth); // could yield directly but using `call` makes testing eaiser
}
export default function* request(url, method, headers, body, auth = null) {
const options = { method, headers, body };
const token = auth ? yield select(makeSelectToken()) : null;
try {
const response = yield call(fetch, url, addHeader(options, token));
const checkedResponse = checkStatus(response);
return parseJSON(checkedResponse);
} catch (e) {
const errorEffect = getErrorEffect(e); // replaces handleError
if (errorEffect) {
yield errorEffect;
}
}
}
function addHeader(options = {}, token) {
const newOptions = { ...options };
if (!options.headers) {
newOptions.headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
...options.headers,
};
}
if (token) {
newOptions.headers.Authorization = `Bearer ${token}`;
}
return newOptions;
}
function getErrorEffect(error) {
if (error.code === 401) {
return put(push('/login')); // returns the effect for the `request` generator to yeild
}
if (error.code == 400) {
return displayToast(error); // assuming `displayToast` is an effect that can be yielded directly
}
}
So I am using axios to call my server and get response, and tried it with redux-saga but with no success. When I console log inside my axios call I got response, but signInUser in yield call is undefined forever. What can be wrong here?
const signInUserM = async (email, password) => {
await axios
.get("https://localhost:44320/Account/token")
.then(async function(response) {
const { data } = response;
axios.defaults.headers.common = {
Authorization: `Bearer ${data.token}`
};
await axios
.post("https://localhost:44320/Login", {
email: email,
password: password
})
.then(authUser => {
console.log(authUser); // got response
return authUser;
})
.catch(error => {
console.log(error);
return error;
});
})
.catch(error => {
console.log(error);
return error;
});
};
function* signInUserG({ payload }) {
const { email, password } = payload;
try {
const signInUser = yield call(
signInUserM,
email,
password
);
console.log(signInUser); // undefined forever
if (signInUser) {
// never gets here
yield put(userSignInSuccess(signInUser.id));
}
} catch (error) {
console.log(error);
}
}
Thanks for any help!
You forgot return in signInUserM and in front of the other await as well I think.
So I am trying to create a user using redux-form. I have an express post route on the backend. NOTE: using redux-thunk for middleware, whatwg-fetch with webpack and babel-polyfill.
routes.post('/signup', async (req, res) => {
try {
const createdUser = await userController.createUser(req.body);
const JSONCreatedUser = JSON.stringify(createdUser);
res.json({
confirmation: 'success',
result: createdUser,
});
return JSONCreatedUser;
} catch (error) {
res.statusMessage = error.toString();
res.status(409).json({
confirmation: 'failure',
error: error.toString(),
});
}
});
So the problem I am having is that when I use postman. I will get the entire user object back.
But when I submit it using form I only get
Apimanager.js
export const signUserUpApi = async (url, params) => {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const { status, statusText } = response;
if (status === 409) {
throw new Error(statusText);
}
return response;
} catch (error) {
throw new Error(error.toString());
}
};
action.js
import constants from '../constants';
import { signUserUpApi } from '../utils/APIManager';
const signUserUpUrl = process.env.SIGN_USER_UP_URL || 'http://localhost:3000/user/signup';
export const signUserUp = (user) => {
return async (dispatch) => {
try {
const createdUser = await signUserUpApi(signUserUpUrl, user);
dispatch({
type: constants.SIGN_USER_UP,
user: createdUser,
});
return createdUser;
} catch (error) {
throw new Error(error);
}
};
};
export const signUserIn = (user) => {
return {
type: constants.SIGN_USER_UP,
user,
};
};
What I am trying to do is to get the User Object I created when I submit the form and redirect back to the page.
This is what I get back and it did create the user.
First thing, I need is why am I getting the https status code back and not the user object?
Second thing, what are the ways to redirect to the home page when a user successfully signed up logged in.