Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling errors with react-apollo useMutation hook

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!

like image 434
d_bhatnagar Avatar asked Dec 24 '19 08:12

d_bhatnagar


People also ask

How do you handle errors in Apollo GraphQL?

By default, Apollo Client throws away partial data and populates the error. graphQLErrors array of your useQuery call (or whichever hook you're using). You can instead use these partial results by defining an error policy for your operation. If the response includes GraphQL errors, they are returned on error.

Does Apollo cache errors?

Apollo will pull data from the cache, but not errors. error ends up being undefined. Create a query that results in an error and data. Navigate away from component and then back and notice that the data will get pulled from cache, but errors will not.

Does useMutation return a promise?

A function that performs an asynchronous task and returns a promise.

How do you send an error response in GraphQL?

To add errors to your data, you need to use the Union type (a.k.a. Result ) in your GraphQL schema. Also, the logic in the resolver is rewritten so that next to the result or error for the operation you also need to return a type for it.


2 Answers

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.

like image 53
Daniel Rearden Avatar answered Sep 30 '22 14:09

Daniel Rearden


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.

like image 39
vipin goyal Avatar answered Sep 30 '22 14:09

vipin goyal