Handling errors with react-apollo useMutation hook - reactjs

I have been trying to get my head around this problem but haven't found a strong answer to it. I am trying to execute a login mutation using the useMutation hook.
TLDR; I want to know what exactly is the difference between the onError passed in options and error given to me by the useMutation
Here's my code snippet
const [login, { data, loading, error }] = useMutation(LOGIN_QUERY, {
variables: {
email,
password
},
onError(err) {
console.log(err);
},
});
On the server-side, I have a preset/hardcoded email used for login and I am not using Apollo or any other client. In the resolver of this Login Mutation, I simply throw an error if the email is not same using
throw new Error('Invalid Email');
Now I want to handle this error on the client-side (React). But my concern is that if I use the 'error' returned from the useMutation hook and try to show the error in this way
render() {
...
{error && <div> Error occurred </div>}
...
}
the error is updated in the UI but then immediately React shows me a screen with:
Unhandled Rejection (Error): Graphql error: My-custom-error-message
But, if I use onError passed in options to useMutate function, then it doesn't show me this screen and I can do whatever I want with the error.
I want to know what exactly is the difference between the onError passed in options and error given to me by the useMutation and why does React show me that error screen when onError is not used.
Thanks!

Apollo exposes two kinds of errors through its API: GraphQL errors, which are returned as part of the response as errors, alongside data, and network errors which occur when a request fails. A network error will occur when a server can't be reached or if the response status is anything other than 200 -- queries that have errors in the response can still have a status of 200. But an invalid query, for example, will result in a 400 status and a network error in Apollo Client.
Apollo Client actually provides four different ways to handle mutation errors:
1.) Calling the mutate function returned by the hook returns a Promise. If the request is successful, the Promise will resolve to a response object that includes the data returned by the server. If the request fails, the Promise will reject with the error. This is why you see an "Unhandled Rejection" message in the console -- you need to handle the rejected Promise.
login()
.then(({ data }) => {
// you can do something with the response here
})
.catch(e => {
// you can do something with the error here
})
or with async/await syntax:
try {
const { data } = await login()
} catch (e) {
// do something with the error here
}
By default, the Promise will reject on either GraphQL errors or network errors. By setting the errorPolicy to ignore or all, though, the Promise will only reject on network errors. In this case, the GraphQL errors will still be accessible through the response object, but the Promise will resolve.
2.) The only exception to the above occurs when you provide an onError function. In this case, the Promise will always resolve instead of rejecting, but if an error occurs, onError will be called with the resulting error. The errorPolicy you set applies here too -- onError will always be called for network errors but will only be called with GraphQL errors when using the default errorPolicy of none. Using onError is equivalent to catching the rejected Promise -- it just moves the error handler from the call site of the mutate function to the call site of the hook.
3.) In addition to the mutate function, the useMutation hook also returns a result object. This object also exposes any errors encountered when running the mutation. Unlike the error handler functions we wrote above, this error object represents application state. Both the error and data objects exposed this way exist as a convenience. They are equivalent to doing this:
const [mutate] = useMutation(YOUR_MUTATION)
const [data, setData] = useState()
const [error, setError] = useState()
const handleClick = async () => {
try {
const { data } = await mutate()
setData(data)
catch (e) {
setError(e)
}
}
Having error state like this can be useful when you want your UI to reflect the fact there's an error. For example, you might change the color of an element until the mutation runs without an error. Instead of having to write the above boilerplate yourself, you can just use the provided result object.
const [mutate, { data, error }] = useMutation(YOUR_MUTATION)
NOTE: While you can use the exposed error state to update your UI, doing so is not a substitute for actually handling the error. You must either provide an onError callback or catch the error in order to avoid warnings about an unhandled Promise rejection.
4.) Lastly, you can also use apollo-link-error to add global error handling for your requests. This allows you to, for example, display an error dialog regardless of where in your application the request originated.
Which of these methods you utilize in your application depends heavily on what you're trying to do (global vs local, state vs callback, etc.). Most applications will make use of more than one method of error handling.

const [mutationHandler, { data, loading }] = useMutation(YOUR_MUTATION, {
onError: (err) => {
setError(err);
}
});
With this we can access data with loading status and proper error handling to avoid any error in console / unhandled promise rejection.

const YOUR_COMPONENT = ({ setError }) => {
// ...
const [mutationHandler, { data, loading }] = useMutation(YOUR_MUTATION, {
onError: (error) => {
setError(error.graphQLErrors[0].message)
}
})
This will handle the errors

Related

What's the correct way of typing an error in react-query?

I want to be able to provide a custom error (properly typed) to react-query's useQuery hook. The reason for this custom error is to be able to provide to it the status code of the response in case it fails, so I can determine whether or not to use the useErrorBoundary option based on that status.
I've read that it's not a very good idea to provide generics to useQuery like so:
const { data, isLoading } = useQuery<DataType, CustomErrorType>(
['example'],
() => getData(),
{
useErrorBoundary: (error) => error.status >= 500
}
)
And that it's a better idea to let react-query infer the types by typing the queryFn instead. If I were using axios this wouldn't be an issue since it provides some utilities and types for errors, but I'm working with fetch.
So, what would be the best way to get a correctly typed custom error in this case?
the Error type can be inferred from all "places", so it works if you provide a type annotation to useErrorBoundary:
const { data, isLoading } = useQuery(
['example'],
() => getData(),
{
useErrorBoundary: (error: CustomErrorType) => error.status >= 500
}
)
this will make the type of Data still be inferred from the queryFn (and eliminate all other problems that come from passing generics manually), and error will still be CustomErrorType
TypeScript playground here

Getting user data with req.body inside a useEffect that then displaying inside a table

I got a problem when I try the normal fetch method inside my react app not returning anything and think something to do with my req.body. I have tried using axios.get but giving me varouis errors.
See below my code:
useEffect with fetch: (Don't return anything)
React.useEffect(()=>{
const manager = {
"manager":managerName
}
fetch(`http://localhost:5000/staff/getStaffM`,manager)
.then(resp=>resp.json())
.then(data=>setData(data))
},[]);
useEffect with axios.get: (returning the following error = Unhandled Promise Rejection: TypeError: resp.json is not a function. (In 'resp.json()', 'resp.json' is undefined) )
React.useEffect(()=>{
const manager = {
"manager":managerName
}
axios.get(`http://localhost:5000/staff/getStaffM`,manager)
.then(resp=>resp.json())
.then(data=>setData(data))
},[]);
Backend Routing:
const getStaffM = asyncHandler(async(req,resp)=>{
const staff = await Staff.find({employeeManager: req.body.manager})
if(staff){
resp.status(200).json(staff)
}
else{
resp.status(400)
throw new Error ("Could not find this manager")
}
This is working when I try it in post man but not in React app.
See below successful postman screenshot:
Postman request

React.js Log all GraphQL queries in Sentry

I have a React application which I've surrounded with an ErrorBoundary that sends errors to Sentry and it works fine. I would like to log all my GraphQL query errors into Sentry as well but my problem now is for all my GraphQL queries, I have a catch block where I dispatch an action for the failed query.
When I remove the catch blocks, the errors are logged into Sentry but I'm unable to trigger the failed query action.
My solution now is to put Sentry.captureException() into each catch block of a GraphQL query which is very repetitive.
Is there a way to allow the ErrorBoundary to still catch GraphQL errors even if the query has it's own catch block?
function getEmployee() {
return function(dispatch) {
dispatch(requestEmployeeInformation());
GraphqlClient.query({ query: EmployeeQuery, fetchPolicy: 'network-only' })
.then((response) => {
dispatch(receiveEmployeeInformation(response.data));
})
.catch((error) => {
/* temporary solution. This sends error to sentry but is very repetitive because
it has to be added to every single action with a graphql query
*/
Sentry.captureException(error)
//dispatch this action if the query failed
dispatch(failGetEmployee(error));
});
};
}
You can always throw the error again inside the catch block. However, the best way to handle this is by using Error Link. This will allow you to log both GraphQL errors (errors returned as part of the response) as well as network errors (failed requests, invalid queries, etc.).
import { onError } from '#apollo/client/link/error'
const link = onError(({ graphQLErrors, networkError, response }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
Sentry.captureMessage(message)
)
if (networkError) {
Sentry.captureException(networkError)
}
// Optionally, set response.errors to null to ignore the captured errors
// at the component level. Omit this if you still want component-specific handling
response.errors = null
});

Apollo error handling - why react app crashes?

is somebody able to explain, why my react app + apollo behaves like this when I try to use mutation which returns error?
GraphQL mutation returns this (response code is 200): {"errors":[{"error":{"result":"identity.not-found","error":"authentication-failed","statusCode":401}}],"data":{"login":null}}
My mutation looks like this:
export const LOGIN_MUTATION = gql`
mutation($input: LoginInput!) {
login(input: $input) {
token
}
}
`;
called:
const handleSignIn = () => {
loginMutation({
variables: {
input: {
clientId: config.clientId,
username: userName,
password: password,
clientSecret: config.clientSecret
}
}
});
};
It behaves for awhile like expected (my own custom error component is rendered - {error && <div>error</div>}), but then it throws this unhandled rejection.
If I add catch callback to mutation call, it works as expected.
However, I did not find anywhere in apollo docs any mentions about the need to always catch GraphQL errors such way. This should be sufficient, if I understand it correctly: const [loginMutation, {data, loading, error}] = useMutation(LOGIN_MUTATION);
Is this behaviour correct or do I miss something?
Versions:
"#apollo/react-hooks": "^3.1.3"
"apollo-boost": "^0.4.7"
"graphql": "^14.5.8"
The mutate function returns a Promise that, by default, will be rejected if your response includes an errors object or if a network error occurs. If you change the errorPolicy to ignore or all, the presence of errors in your response won't cause the Promise to reject, but a network error still will.
The only way to avoid this behavior is to provide an error handler through the onError parameter. If onError is provided, the Promise returned by mutate will always resolve and any relevant errors (depending on your errorPolicy) will be passed to onError instead.
Additional details on error handling in Apollo can be found here. Additional details on unhandled Promise rejections can be found here.

Handling AJAX Errors with Redux Form

I'm new to React/Redux so I'm building a simple blog app with Redux Form to help me learn. Right now I'm unclear on how I would handle ajax errors when submitting data from the form to the api in my action. The main issue is that I'm using the onSubmitSuccess config property of Redux Form and it seems to always be called, even when an error occurs. I'm really unclear on what triggers onSubmitSuccess or onSubmitFail. My onSubmitFail function is never executed, but my onSubmitSuccess function always is, regardless of whether an error occurred or not.
I read about SubmissionError in the redux-form docs, but it says that the purpose of it is "to distinguish promise rejection because of validation errors from promise rejection because of AJAX I/O". So, it sounds like that's the opposite of what I need.
I'm using redux-promise as middleware with Redux if that makes any difference.
Here's my code. I'm intentionally throwing an error in my server api to generate the error in my createPost action:
Container with my redux form
PostsNew = reduxForm({
validate,
form: 'PostsNewForm',
onSubmit(values, dispatch, props) {
// calling my createPost action when the form is submitted.
// should I catch the error here?
// if so, what would I do to stop onSubmitSuccess from executing?
props.createPost(values)
}
onSubmitSuccess(result, dispatch, props) {
// this is always called, even when an exeption occurs in createPost()
},
onSubmitFail(errors, dispatch) {
// this function is never called
}
})(PostsNew)
Action called by onSubmit
export async function createPost(values) {
try {
const response = await axios.post('/api/posts', values)
return {
type: CREATE_POST,
payload: response
}
} catch (err) {
// what would I do here that would trigger onSubmitFail(),
// or stop onSubmitSuccess() from executing?
}
}
In your case, redux-form doesn't know whether form submission was succeeded or not, because you are not returning a Promise from onSubmit function.
In your case, it's possible to achieve this, without using redux-promise or any other async handling library:
PostsNew = reduxForm({
validate,
form: 'PostsNewForm',
onSubmit(values, dispatch, props) {
// as axios returns a Promise, we are good here
return axios.post('/api/posts', values);
}
onSubmitSuccess(result, dispatch, props) {
// if request was succeeded(axios will resolve Promise), that function will be called
// and we can dispatch success action
dispatch({
type: CREATE_POST,
payload: response
})
},
onSubmitFail(errors, dispatch) {
// if request was failed(axios will reject Promise), we will reach that function
// and could dispatch failure action
dispatch({
type: CREATE_POST_FAILURE,
payload: errors
})
}
})(PostsNew)
For handling asynchronous actions you should use redux-thunk, redux-saga or an other middleware which makes it possible to run asynchronous code.

Resources