How to handle bad request in fetch() - reactjs

I know this is a basic and often recurring issue, but I am still unable to make it work.
I have the following code
handleAdd = (event: any): void => {
// ...
// create new task
try {
// send data to backend
fetch(`/api/tasks?name=${name}&priority=${priority}`, { method: 'post' })
.then(response => { if (!response.ok) {
throw new Error('error => how to get bad request message here?') } })
}
// handle exception
catch (e) {
console.log(e);
this.setState({
isError: true,
errorMessage: e.message
});
}
}
the API returns 400 with some error message
but in the browser I get
So I have two questions
Why the throw new Error() in fetch does not goes to catch(e) {} method (if the error is outside fetch function, it works well)? How to rewrite this line to get into catch method? I think it has something to do with the Promise object?
How to get the bad request message from response object?
update, working solution
fetch(`/api/tasks?name=${name}&priority=${priority}`, { method: 'post' })
.then(response => {
if (!response.ok) {
response.text().then(function (text) {
throw Error(text);
}).catch((e) => {
this.setError(e.message);
});
}
})
how to get response.text() into the Error exception?

when using promises, you can choose between chaining your promise with then/catch or using async/await.
if you are chaining promise you should chain with a catch:
handleAdd = (event: any): void => {
// send data to backend
fetch(`/api/tasks?name=${name}&priority=${priority}`, { method: 'post' })
.then(response => { if (!response.ok) {
throw new Error('error => how to get bad request message here?') } }
).catch((e) => {
console.log(e);
this.setState({
isError: true,
errorMessage: e.message
})
});
}
if you prefer you can change your function to async/await. with that you would use a try/catch block:
// mark your function as async
handleAdd = async (event: any): void => {
try {
// await your fetch response
const response = await fetch(`/api/tasks?name=${name}&priority=${priority}`, { method: 'post' })
if (!response.ok) {
throw new Error('error => how to get bad request message here?')
}
}
// handle exception
catch (e) {
console.log(e);
this.setState({
isError: true,
errorMessage: e.message
});
}
}

I'd imagine if you are not using React, it could be that you have a local or global error or warning message area, so that the
fetch
.then()
.catch(err => {
// display "try again later" here
});
But since you are using React and probably Redux, you could dispatch an action NETWORK_ERROR instead so that the reducer will create that error message "try again later":
fetch
.then()
.catch(err => {
// dispatch the action for NETWORK_ERROR here
});

try it this way and you are good to go
fetch(`/api/tasks?name=${name}&priority=${priority}`, { method: 'post' })
.then(response => { if (!response.ok) {
throw new Error('error => how to get bad request message here?') }
}).catch(error => {
// handle the error here
console.log(e);
});

Related

Axios does not catch error even not enter in catch block

I am trying to get the error status code that would be 413 in Axios catch block. I have tried different solutions nothing worked for me. Could you please review what is going wrong.
uploadNewDatDocuments(datId, files = [], additionalInfo = {}) {
return new Promise((resolve, reject) => {
let url = new URL(this.baseUrl + this.uploadDocument.replace('{id}', datId));
Object.keys(additionalInfo).forEach(queryParam => url.searchParams.set(queryParam, additionalInfo[queryParam]));
let formData = new FormData();
files.forEach(file => formData.append('files', file));
axios
.post(url.toString(), formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then(response => {
resolve(response.data);
})
.catch(error => {
console.log("error occurred")
reject(error);
}).finally(error=>{
console.log(error);
})
});
}
Here is my Action code.
export function uploadNewDocuments(datId, additionalInfo = {}, attachments = [], comment = {}) {
return dispatch => {
datService
.uploadNewDatDocuments(datId, attachments, additionalInfo)
.then(response => {
const attachmentsIds = response.map(attachment => attachment.id);
dispatch(
DatCommentActions.addDatNewComment(datId, {
...comment,
message: { ...comment.message, attachments: attachmentsIds }
})
);
})
.catch(error => {
dispatch(MessageActions.showMessage({ message: error.response.data.message }));
console.error(error);
});
};
}
413 Request Entity Too Large is not actually error, its a not successful response and catch wont fire unless there is actual error on response.
What you could do is check response.status and based on that and write own error handling.

How to send a response in a callBack

I'm pretty new to the fetch api and cant figure out why I can access response.json() locally but not once deployed.
EDIT: Just making the question clearer. When my response is NOT ok, I want to send response.json() to my errorCallBack function. This works locally and I set my error message to 'MyException'. However, when it is deployed to a new environment the parameter received by errorCallBack is a Promise with the status - 'Pending' and the catch in the errorCallBack function is hit.
Fetch:
fetch(request)
.then(response => {
if (!response.ok) {
errorCallBack(response.json());
return;
}
successCallBack();
})
.catch(error => {
console.log(error.message);
errorCallBack();
});
Error callback:
errorCallBack = (jsonResponse) => {
jsonResponse
.then(data => {
if (data) {
if (data.MyException) {
this.setErrorMessage("MyException");
return;
}
}
})
.catch(() => {
this.setErrorMessage("");
});
}
Some changes. response.json() can fail if you are returning an empty response, but you can add your logic there.
fetch(request)
.then(response => {
if (!response.ok) {
console.log(response.status) // Error code
console.log(response.statusText); // Error message
return errorCallBack(response.json()); // return response Promise
}
return successCallBack(response.json()); // This will return a Promise
})
.catch(error => {
console.log(error); // Log any error
errorCallBack(error); // return an error object
});
handle Error
errorCallBack = (response) => {
response
.then(jsonData => {
if(jsonData.myException) {
...
} else{
...
}
})
.catch(error => {
console.log(error)
...
})
}

TypeError: Cannot read property 'setState' of undefined/XML

I am calling service that is using XML, so i want to parse XML to JSON and the JSON data set as my react state but i am getting.
TypeError: Cannot read property 'setState' of undefined
axios
.get(session_url)
.then(function(response) {
parseString(response.data, (err, result) => {
if (err) {
throw err;
} else {
this.setState({
odm: result.data,
loading: false,
});
}
});
})
.catch(function(error) {
console.log(error);
});
You're mixing and matching function()s that don't capture this and arrow (=>) functions which do.
Simply use arrow functions everywhere and the this (that's your React component) will be properly captured:
axios
.get(session_url)
.then(response => {
parseString(response.data, (err, result) => {
if (err) {
throw err;
} else {
this.setState({
odm: result.data,
loading: false,
});
}
});
})
.catch(error => {
console.log(error);
});
Better yet, if you can, use an async function:
// promisified version of `parseString`:
const parseStringP = data =>
new Promise((resolve, reject) =>
parseString(response.data, (err, result) => {
if (err) return reject(err);
resolve(result);
}),
);
// ...
try {
const response = await axios.get(session_url);
const result = await parseStringP(response.data);
this.setState({
odm: result.data,
loading: false,
});
} catch (error) {
console.log(error);
}

Rewrite fetch call to oboe for json streams with Typescript

I have this fetch call:
api<T>(url: string, headers: Request): Promise<T> {
return fetch(url, headers)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json().then(data => data as T);
})
.catch((error: Error) => {
throw error;
});
}
componentDidMount(){
this.api<Array<Response>>(url, requestData)
.then(data => {
this.setState({
jobs: data
});
})
.catch(error => {
console.error(error);
});
}
But the response that I get is a stream+json so I get invalid json at .json().
I saw that there is a library that can help me: http://oboejs.com/examples
But I'm having issues using oboe and typescript (beginner) (using https://www.npmjs.com/package/#types/oboe).
I tried:
api<T>(headers: Request): Oboe<T> {
return oboe(headers)
.done(function(response) {
return response;
})
.fail(function(error: Error) {
throw error;
});
}
componentDidMount(){
this.api<Array<Response>>(requestData)
.done(data => {
this.setState({
jobs: data
});
})
.fail(error => {
console.error(error);
});
}
But there are obvious errors as I don't know what type oboe should return so I get an error Oboe is not generic.
The error means that the Oboe class/type is not generic. Like Number of String for example
From Oboe's docs it seems that oboe(param).done() takes a callback
You can transform that call into a Promise and do the rest the same way you used to do
Replacing the callback logic with a Promise
api<T>(headers: Request): Promise<T> {
return new Promise((resolve, reject) => {
oboe(headers)
.done(data => resolve(data))
.fail(err => reject(err));
});
}
Calling it (the way you did with Promise/fetch)
componentDidMount(){
this.api<Array<Response>>(url, requestData)
.then(data => {
this.setState({
jobs: data
});
})
.catch(error => {
console.error(error);
});
}

Handle Error response React.js

My Spring boot Controller method:
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<APIResponseMessage> testMethod(#RequestBody MyPojo myPojo) {
APIResponseMessage resp = new APIResponseMessage();
try {
serviceObj.callServiceMethod(myPojo);
resp.setMessage("successfull!");
} catch (Exception e) {
resp.setMessage("failed!");
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(resp);
}
return ResponseEntity.ok(resp);
}
React action handler class has the following method:
export default (data) => (dispatch) => {
dispatch({
type: initHandler
});
fetchJSON(url, 'POST', data)
.then((json) => {
dispatch({
type: successHandler,
apiResponse: json
})
})
.catch((error) => {
dispatch({
type: failureHandler,
apiResponse: error,
apiMessage : "System encountered error. Please try again later."
})
})
}
And fetchJSON is define in one of my util classes in react as :
export const checkStatus = response => {
const hasError = (response.status < 200 || response.status >= 300)
if (hasError) {
const error = new Error("This is an error") // I want to set my message that I obtained from the controller here.
throw error
}
return response
}
export const parseJSON = response => response.json()
export const fetchJSON = (url, method, data) => {
return fetch(url, {
method: method,
headers: new Headers({
'Content-Type': 'application/json'
}),
body: JSON.stringify(data)
}).then(checkStatus).then(parseJSON);
}
I want to set the custom message that I get from my API to be set to the error object. I tried many options but couldn't make it to work.
The problem is how the Promise is being resolved, or rather, not resolved when you try to use it. Calls to 'response.json()' return a promise, during the normal flow of execution when you don't 'throw' an error, this promise is resolved, and you can work with the result.
However, when the error is thrown, you need to resolve, or '.then()' the error in the catch block.
I think this should work for you, first throw your response.text() in the checkStatus function:
if (hasError) {
throw response.json()
}
Since you are throwing an error in a Promise, the nearest catch, or rejection callback is invoked:
.catch((error) => {
dispatch({
type: failureHandler,
apiResponse: error,
apiMessage : "System encountered error. Please try again later."
})
})
'error' in this case is the unresolved Promise created by calling 'response.text()', so you can resolve this by wrapping the 'dispatch' in error.then() as follows:
.catch((error) => { // error is a Promise
error.then((e) => {
dispatch({
type: failureHandler,
apiResponse: e, // e is now the resolved value of 'response.text()'
apiMessage : "System encountered error. Please try again later."
});
});
})
There is a simplified jsfiddle of this here: https://jsfiddle.net/LLL38vea/

Resources