Josh Thomas
Josh Thomas

Reputation: 1677

How to prevent error propagation in Apollo Client useQuery in react?

I'd like to catch the error in component level and prevent propagation while using the useQuery in @apollo/react-hook. Here is my example code

const invitationDocument = gql`
   query DecodeInvitation($token: String!) {
       DecodeInvitation(token: $token) {
          name
          email
       }
   }
`
const InvitationPage = (props) => {
    const { data, error, loading } = useQuery(invitationDocument, {variables: { token: "XXXX" }});

    if(error)
    {
        return <InvitationErrorPage error={error.message}/>
    }

    return loading? <LoadingPage> : <InvitationAcceptPage />
}

It works fine but at the same time, the error is being propagated to its parents level so I get another error notification message which comes from the error handler at the global level. At the application level, I use the apollo-link-error to manage the Graphql errors.

import { onError } from 'apollo-link-error';
const errorLink = onError (({ graphqlErrors, networkError }) => {
    if(graphqlErrors)
       notification.error(graphqlErrors[0].message);
});
const client = ApolloClient({
   cache: new InMemoryCache(),
   link: ApolloLink.from([
            errorLink,
            new HttpLink({ uri: `http://localhost:8080/graphql`})
          ])
})

For now, I am finding a solution to stop propagation to top-level so that I can show only InvitationErrorPage and stop displaying error notification at the global level.

Upvotes: 1

Views: 3098

Answers (2)

Luciano Corniglione
Luciano Corniglione

Reputation: 511

I hope this could help anybody. In my case I did the following:

On the component I'm performing the query:

const { data, loading } = useQuery({
            variables: { ...whatever variable },
            context: { errorHandledLocally: true }, // We don't want to handle this error globally
            onError: (error) => {
                // Do whatever you want with the error
                // In my case I want to show a toast with an specific error
            }
    })

Then, in the apollo client, I have the following link:

const errorLink = onError(({ graphQLErrors, networkError, operation }) => {
        if (graphQLErrors) {
            graphQLErrors.map(({ message }) => Sentry.captureException(message))
            if (operation.getContext().errorHandledLocally) return // Check this into the queries performed throughout the app

            toaster.negative(default_error)
        }

        if (networkError) {
            toaster.negative(network_error)
            Sentry.captureException(networkError)
        }
    })

Basically with the flag errorHandledLocally I'm making sure that I'm not rendering two toasts in this case.

Upvotes: 0

dr00
dr00

Reputation: 73

I was also trying to prevent errors from being logged in an Error Link by handling them on a useQuery hook and a further delve into the ApolloLink documentation helped clear up what is happening. The key misunderstanding is that the Error Link is not a parent- or application-level handler, it is request middleware. It's helpful to think about how the data is coming back from the server:

Data flow to client through ApolloLink path.

Thus, when you see an error notification from the Error Link it is not something that "propagated up" from the useQuery hook: it occurred in the request path before the useQuery result was available on the client.

Thus, the onError callback for the Error Link will always be called before any error handling code in the useQuery hook.

Probably your best bet is to use a combination of the operation and graphQLErrors[x].extensions to figure out what errors you should pass through the Error Link middleware like so:

const errorLink = onError(({operation, response, graphQLErrors}) => {
  if (!graphQLErrors) {
    return;
  }
  if (operation.operationName === "DecodeInvitation") {
    for (const err of graphQLErrors) {
      if (err.extensions?.code === 'UNAUTHENTICATED') {
        // Return without "notifying"
        return;
      }
    }
  }
  // Notify otherwise
  notification.error(graphqlErrors[0].message);
})

Upvotes: 4

Related Questions