How handle Fetch failures in Redux's action creators - reactjs

Redux docs suggest to have 3 different actions per AJAX request. For example for Login they would be:
LOGIN_REUQEST
LOGIN_FAILURE
LOGIN_SUCCESS
But I have problem with catching all errors that can be thrown by fetch.
In redux docs I found this example:
return fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(
response => response.json(),
// Do not use catch, because that will also catch
// any errors in the dispatch and resulting render,
// causing an loop of 'Unexpected batch number' errors.
// https://github.com/facebook/react/issues/6895
error => console.log('An error occured.', error)
)
.then(json =>
// We can dispatch many times!
// Here, we update the app state with the results of the API call.
dispatch(receivePosts(subreddit, json))
)
}
But I see several problems in it:
It doesn't use catch, so it wont catch any problems with AJAX request on user side (for example no internet)
It doesn't handle responses with status != 200.
I ended up this this code, but still it won't handle case #1 (because of React issue - see comment in above code):
fetch('/api/auth/signin', {...}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/plain, */*',
}
})
.then ( response =>
response.json().then(json => {
if (response.ok) {
dispatch(loginSuccess(json))
} else {
dispatch(loginFailure(json.errMsg))
}
})
)
Can you please give me any example which handles all possible errors with AJAX request using fetch. Many tutorial and open-source projects just ignoring it.

You don't need .catch() to handle offline errors. The second argument to .then(), the error callback, will catch offline (no internet connection) errors.
And, handling responses that are not 200 is straightforward:
fetch({...})
.then(
response => {
if (response.status !== 200) {
dispatch(loginFailed(response.json()));
return Promise.reject();
} else {
return response.json();
}
},
error => {
dispatch(loginFailed(error.json()));
return Promise.reject();
}
)
.then(json => {
dispatch(loginSuccess(json));
});

async function fetchLists(url, token, userName, etag, dataToUpdate) {
var responseJSON;
try {
let response = await fetch(url,
{
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Access-Token': token,
'X-Key': userName,
'If-None-Match': etag
}
}
);
switch (response.status) {
case 200:
var data = await response.json();
var dataType = getListForUpdate(dataToUpdate);
responseJSON = {
action: dataType,
message: UPDATE_DATA,
response: response,
responseJSON: data
}
break;
case 304:
var dataType = getListUptoDate(dataToUpdate);
responseJSON = { action: dataType, message: DATA_UPTO_DATE };
break;
case 404:
responseJSON = { message: NOT_FOUND };
break;
case 500:
responseJSON = { message: SERVER_ERROR };
break;
case 401:
case 402:
responseJSON = { message: INVALID_CREDENTIALS };
break;
}
// console.log(response);
} catch (err) {
// console.log(err);
responseJSON = { message: NETWORK_REQUEST_FAILED };
// console.log(responseJSON);
}
return responseJSON;
}
This code structure maybe able to solve your problem. I have recorded all the responses and left catch for network request failures. Comment if you don't understand any of it.
Hope it helps.

Related

axios post / get always return 404 in react

// Login Method
const postLogin = (url, data) => {
return axios.post(url, data).then(response => {
if (response.status === 400 || response.status === 500)
throw response.data;
console.log(response);
return response;
}).catch(err => {
console.log(err);
throw err[1];
});
}
The above login method always log the following error:
Error: Request failed with status code 404
at createAxiosError (utils.js:147:1)
at Object.settle (utils.js:123:1)
at handleRequest (handle_request.js:124:1)
at index.js:26:1
at new Promise (<anonymous>)
at MockAdapter.<anonymous> (index.js:25:1)
at dispatchRequest (dispatchRequest.js:52:1)
On call
const response = yield call(postLogin, 'http://localhost:8080/users/verify_login/', {
email: user.username,
password: user.password,
headers: {
"Content-Type": "application/json",
"accept" : "application/json",
}});
Though the http://localhost:8080 is running. We are using React. How to solve this?
You can try using async await for better debugging your code.
const postLogin = async (url, data) => {
try{
var response = await axios.post(url, data)
return response
}catch(e){
// Handle catch
}finally{
// Handle finally
}
}

How do I capture the response JSON from a 400 bad request with fetch?

I'm using React 17. I want to submit a fetch request and parse the error JSON that comes back in the event of a 400. I have this code
fetch(REACT_APP_PROXY + "/save_to_sheet_from_form/", {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: formData,
})
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw response;
}
})
.then(function (data) {
window.location.reload();
})
.catch((err) => {
err.json().then((errorMessage) => {
try {
setErrors(JSON.parse(errorMessage));
} catch (e) {
return;
}
});
});
In my network tab, I can see this response when there is a 400
{"coop_name":["This field may not be blank."],"street":["This field may not be blank."],"city":["This field may not be blank."],"zip":["This field may not be blank."],"contact_name":["This field may not be blank."],"contact_email":["This field may not be blank."],"contact_phone":["This field may not be blank."]}
However the above line
err.json().then((errorMessage) => {
is throwing a syntax error. What's the proper way to retrieve the body of the response?
If the response, when not OK, may still be JSON-parseable, then using .json on it will have the browser attempt to parse the result as JSON.
You need to account for a few separate categories:
The initial request failed (in which case the first .then will not be entered into at all, and the .catch will be entered into instead)
The request was bad, and the server responded with parseable JSON
The server had a problem and sent a bad response back (such as 500 Internal Server Error)
The request succeeded
You can put most of the logic testing for this while parsing the header of the response. Something along the lines of
fetch(REACT_APP_PROXY + "/save_to_sheet_from_form/", {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: formData,
})
.then((response) => {
if (response.ok) {
// will succeed unless server logic or your logic is off
return response.json().then((data) => {
window.location.reload();
});
} else if (response.status === 400) {
// will succeed if the server will always respond with JSON with a 400 response
return response.json().then((errorObj) => setErrors(errorObj));
} else {
// there was some other error in the response, such as status 500
setErrors(response.statusText);
}
})
.catch((err) => {
// An unexpected error occurred which was not 400 nor while parsing the response header
setErrors(String(err));
});
If the logic to carry out when the request succeeded or failed isn't entirely trivial, go ahead and use standalone named functions instead to make it more readable instead of putting everything in the first .then. (eg if (response.ok) { return response.json().then(handleSuccess); })

Generic function to request api with Axios

I am trying to build a generic function for my endpoints, using Axios and React. Generic because I have always the same header and I do not want to repeat a lot of code for each of my components.
To do that, I built this function (sorry, a lot of comments that I will remove after of course) :
export const getRequest = ( endpoint ) => axios
.get( env._URL_SERVER_ + endpoint, { headers: getHeaders() } )
.then((res) => {
// Success
console.log(res);
return {error: false, response: res.data};
})
.catch((error) => {
// Error
if (error.response) {
/*
* The request was made and the server responded with a
* status code that falls out of the range of 2xx
*/
console.log(error.response.data);
console.log(error.response.status);
return {error: true, status: error.response.status, data: error.response.data};
} else if (error.request) {
/*
* The request was made but no response was received, `error.request`
* is an instance of XMLHttpRequest in the browser and an instance
* of http.ClientRequest in Node.js
*/
console.log(error.request);
return {error: true, data: error.request };
} else {
// Something happened in setting up the request and triggered an Error
console.log('Error', error.message);
return {error: true, data: error.message}
}
});
Ant then in my components I do that :
getSchools = () => {
this.setState({
loadingSchools: true
}, () => {
getRequest(`/schools?name=${this.state.filterByName}&city=${this.state.filterByCity}&school_type_id=${this.state.filterBySchoolTypeId}&page=${this.state.selectedPage}`)
.then((response) => {
// there is an error
if (!response.error) {
this.setState({
schools: response.response.data,
meta: response.response.meta,
links: response.response.links
})
} else {
this.setState({
error: true,
errorMessage: response.data,
})
}
})
.then(() => {
this.setState({loadingSchools : false});
})
})
}
It works fine. I tested it in several situation (all is OK - 200, not found - 404, no response). But is it a good practice ? I feel that there is a lot of codes in the parent component. Maybe I complicate my life?
Here is how I've done it:
var URL_BACKEND = "http://localhost:5000/";
// Create Function to handle requests from the backend
callToBackend = async (ENDPOINT, METHOD) => {
const options = {
url: `${URL_BACKEND}${ENDPOINT}`,
method: METHOD,
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
};
const response = await axios(options);
return response.data;
}
// Then you make a call with the exact endpoint and method:
const response = await this.callToBackend('createSetupIntent', 'POST');
console.log(JSON.stringify(response));
create one common file for base URL let's say api.js
// api.js file code
export const apiUrl = axios.create({
baseURL: 'http://localhost:5000',
});
Register file
// register.js file code
import { apiUrl } from './api';
try {
const resp = await apiUrl.post('/api/register', {
username,
email,
password,
});
const { data, status } = resp;
if (Object.keys(data).length && status === 200) {
// received api data successfully
console.log('API response', data);
}
} catch (err) {
console.log(err);
}
// For auth request
try {
const token = localstorage.getItem('token');
const res = await apiUrl.post(
'/authroute',
{
name: fullName,
originCountry: country,
career: careerStatus,
},
{
headers: { Authorization: `Bearer ${token}` },
}
);
const { data, status } = strapiRes;
if (Object.keys(data).length && status === 200) {
return res.status(status).json(data);
}
} catch (error) {
throw new Error(error);
}
// same for all request
apiUrl.get(endpoint);
apiUrl.post(endpoint, body);
apiUrl.put(endpoint, body);
apiUrl.delete(endpoint, body);

Fulfilled promise stored rather than the request body

The following code sends a post request to an API and then stores the API's response. The object stored is a fulfilled promise, rather than the body. I though I was using the .then properly, as something similar works for a get request.
The following saga is called every time a post request is made. It contains everything from the post to the call to the call to the reducer.
function* setData(action) {
const url = action.payload.url;
const data_obj = action.payload.data;
console.log(action);
try {
const json = fetch(url, {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(data_obj)
})
.then(statusHelper)
.then(response => response.json());
yield put({ type: "DATA_SENT", payload: json });
}
catch (e)
{
yield put({ type: ERROR_OCCURED, payload: { error : {message: "An Error occured sending HIT data. Please try again later"}}});
}
}
Below is the statusHelper function. It came from another SO answer (I'll edit in the link).
function statusHelper (response) {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
} else {
return Promise.reject(new Error(response.statusText))
}
}
I think your issue is here
.then(response => response.json());
response.json() also returns a promise.
You need to .then off the json() call and return the data.
I was missing a yield after the fetch. This wound up being the solution for me
function* setData(action) {
const url = action.payload.url;
const data_obj = action.payload.data;
let json;
try {
const response = yield fetch(url, {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(data_obj)
});
//const resp = statusHelper(response);
console.log('Response: ', response, response.status)
if(response.status >= 200 && response.status < 300)
{
json = yield response.json();
}
console.log("JSON: ", json);
}
catch (e)
{
yield put({ type: ERROR_OCCURED, payload: { error : {message: "An Error occured sending HIT data. Please try again later"}}});
return;
}
yield put({ type: "DATA_SENT", payload: json });
}

Fetch request always returns network error

I almost finished creating React Native application, few days ago register action has stopped working.. I'm sending fetch request and it always returns network error altough there is 400 response and message that user exists, it stops there..
I'm destructuring the response and displays api response message instead of fetch network error but now it doesn't work. I'm doing the same for the login action and it works.
Could it be something with multipart/form-data ?
export const register = data => dispatch => {
dispatch(createUser());
const d = new FormData();
d.append("name", data.name);
d.append("email", data.email);
d.append("password", data.password);
d.append("avatar", data.avatar);
fetch(API_URL + "/register", {
method: "POST",
headers: {
"content-type": "multipart/form-data"
},
body:d
})
.then(response => response.json().then(user => ({ user, response })))
.then(({ user, response }) => {
if (!response.ok) {
console.log(response, user)
} else {
console.log(response, user)
}
})
.catch(err => {
throw err;
});
};
The api route works in Postman..
In this case, your using fetch which is Promise based incorrectly,
Try,
fetch(API_URL + "/register", {
method: "POST",
headers: { "content-type": "multipart/form-data" },
body:d
})
.then(response => {
console.log(response, response.json().user)
})
.catch(err => {
console.log(err)
});
Check the logs and see if it shows proper network response and debug from there.

Resources