createAsyncThunk throws error but case rejected does not get triggered - reactjs

I am using the following createAsyncThunk to delete an application in the db.
For some reason the fetch is not working. handleFetchErrors throws an Error if the response is not OK
export const handleFetchErrors = (response: Response) => {
if (!response.ok) {
throw Error(response.statusText)
}
}
export const deleteApplication = createAsyncThunk("applications/deleteApplication", async (data: any) => {
try {
const response = await fetch(`/api/application/${data.id}`, {
method: "DELETE",
headers: {
'Accept': 'application/json',
"Content-Type": "application/json",
},
});
//fixme: call still gets fulfilled although I am throwing an error
handleFetchErrors(response);
return (data.id);
} catch (e) {
console.error(e);
}
})
In my createSlice I am using these extraReducers for the upper case
.addCase(deleteApplication.fulfilled, (state, action) => {
state.status = DataFetchingStatus.succeeded
state.applications = state.applications.filter(application => application.id !== action.payload)
})
.addCase(deleteApplication.rejected, (state, action) => {
state.status = DataFetchingStatus.failed
state.error = action.payload
})
The deleteApplication.fulfilled is getting triggered instead of the deleteApplication.rejected.
Am I missing something?

The handleFetchErrors function throws an error, but this error is caught by deleteApplication and merely printed as a console message. Hence, deleteApplication.rejected is never dispatched. I suggest either (a) not catching the error; or (b) re-throwing the error in the catch block.

Related

How to pass status codes to client side? (Redux asyncThunk)

I want to access the status sent from my Express server in my React component. I am using redux to dispatch a login fetch request and want to conditionally render different childrens based on the status code received (eg. 'Incorrect Password' if status code 401)
loginmodal.js
const handleLogin = async (event) => {
event.preventDefault();
let result = await dispatch(login({ email, password }))
console.log(result) //payload is undefined if incorrect password.
}
userAuthSlice.js (Redux)
export const login = createAsyncThunk(
'userAuth/login',
async (payload, thunkAPI) => {
const { email, password } = payload
const result = await fetch(
loginPath, {
mode: 'cors',
credentials: 'include',
method: 'post',
body: JSON.stringify({ email, password }),
headers: {
'Content-Type': 'application/json'
},
}
)
console.log(result) // Response {type: 'cors', url: 'http://localhost:5000/login', redirected: false, status: 401, ok:
const response = await result.json()
console.log(response)
return response
}
)
const userAuthSlice = createSlice({
extraReducers: {
[login.pending]: (state) => {
state.isLoading = true
},
[login.fulfilled]: (state, action) => {
state.isLoading = false
state.isAuth = true
},
[login.rejected]: (state) => {
state.isLoading = false
},
}
server.js
app.post('/login', (req, res) => {
const email = req.body.email;
const plainTextPassword = req.body.password;
User.find({ email: email }).limit(1).exec(function (err, existingUser) {
if (existingUser.length === 0) {
res.sendStatus(401)
} else {
bcrypt.compare(plainTextPassword, existingUser[0].password, function (err, response) {
if (response === true) {
req.session.user = existingUser[0]._id
res.json(req.session)
} else {
res.sendStatus(401)
}
})
}
}
)
})
In my loginModal, i console.log the payload from dispatch. My payload is undefined if my promise is rejected from incorrect password. My payload includes the status code if its fulfilled eg correct password. How can i get the status code / payload, even if the promise is rejected? Or is there another approach to this problem?
You need to check the Response.ok and / or Response.status properties to determine if your request was successful.
If they are not, I would recommend returning a rejected value. The rejected object can have the properties you need like status
const res = await fetch(...);
if (!res.ok) {
// thunkApi.rejectWithValue
return rejectWithValue({
status: res.status,
message: await res.text(),
});
}
return res.json();
Then you can check for rejected promises and refer to the status in your consuming code
const handleLogin = async (event) => {
event.preventDefault();
try {
// needs the `.unwrap()` function call
const result = await dispatch(login({ email, password })).unwrap();
console.log(result);
} catch (err) {
console.warn(err);
switch (err.status) {
case 401:
// do something for 401
break;
default:
// default error handling
}
}
}

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 handle bad request in fetch()

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);
});

Exception not thrown inside save saga

I am working on an SPA with redux-saga state management. My load and save methods themselves are working, yet there is a lot of weird stuff... Below is the saga code:
export function* getEventDetails({ id }) {
const requestURL = `${url}/admin/event/${id}`
try {
const event = yield call(request, requestURL)
yield put(eventLoaded(event, id))
} catch (err) {
yield put(eventLoadingError(err))
}
}
export function* saveEventDetails({ event }) {
const id = event['id']
const requestURL = `${url}/admin/event/${
!isNaN(id) && id !== undefined && id !== null ? id : 'new'
}`
try {
const createdEvent = yield call(request, requestURL, {
method: !isNaN(id) && id !== undefined && id !== null ? 'PUT' : 'POST',
body: JSON.stringify(event)
})
yield put(eventSaved(createdEvent, createdEvent['id']))
yield put(loadEvent(createdEvent['id']))
yield put(loadPreviousEvents())
yield put(loadUpcomingEvents())
} catch (err) {
console.log('caught error inside saga')
yield put(eventSavingError(err))
}
}
export default function* eventsData() {
yield takeLatest(LOAD_EVENT, getEventDetails)
yield takeLatest(SAVE_EVENT, saveEventDetails)
}
One thing is definitely strange - if I turn off the API server then try saving, I never see caught error inside saga in the console. I am therefore unable to dispatch the eventSavingError action, etc.
Where is my error action? In the console I see:
reducer.js:48 action: {type: "project/Container/SAVE_EVENT", event: {…}}
request.js:55 PUT http://localhost:5000/event/10 net::ERR_CONNECTION_REFUSED
The request function:
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response
}
const error = new Error(response.statusText)
error.response = response
throw error
}
export default function request(url, options) {
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Request-Headers': 'Content-Type, Authorization'
}
const token = localStorage.getItem('token')
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
const newOptions = {
...options,
mode: 'cors',
headers
}
return fetch(url, newOptions)
.then(checkStatus)
.then(parseJSON)
}
Using #oozywaters suggestion, I tweaked the code as:
return fetch(url, newOptions)
.then(checkStatus)
.then(parseJSON)
.catch(err => {
throw err
})
It does fix the problem with the missing exception.

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