apollo-link-error return error to caller

Created on 15 Apr 2019  ·  17Comments  ·  Source: apollographql/apollo-link

So I am using Apollo-link-error to manage auth and admin global errors. But if I use it, my catch methods in my promises do not return the errors.

It looks as if all the errors go through apollo-link-error and they are not passed back to the caller method.

Is there a way to return the error to the caller so I can manage some errors locally and some errors globally?

Most helpful comment

I found the solution, kind of weird, but that it is:

This prints just the message

  updateProfile: function() {
      this.$apollo
        .mutate({
          mutation: UPDATE_PROFILE,
          variables: current_user
        })
        .catch((error) => {
          console.log("this prints just the message", error);
        });
    }

BUT this prints the full content of errors

  updateProfile: function() {
      this.$apollo
        .mutate({
          mutation: UPDATE_PROFILE,
          variables: current_user
        })
        .catch(({ graphQLErrors }) => {
          console.log("this prints the full content of 'errors'", graphQLErrors);
        });
    }

I think this issue can be closed
@Nosherwan @romucci @Sceat @lbrdar

All 17 comments

I have the same issue in my app. I want to catch all errors in error link (for example so I could log them) but I also want to forward them to the original caller and handle them over there as they might contain a custom error msg that needs to be displayed to the user.

My code:

  • in provider:
const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    graphQLErrors.forEach(({ message, locations, path }) => {
        console.log(`[GraphQL error]: ${message}, Location: ${locations}, Path: ${path}`);
      },
    );
  }
});

const client = new ApolloClient({
  link: authLink.concat(errorLink).concat(httpLink),
  cache: new InMemoryCache(),
});

  • in component:
 loginMutation({ variables: { data } })
    .then(({ data, errors }) => {
      if (errors) {
        // I WANT TO BE ABLE TO READ ERROR MESSAGE HERE SO I CAN DISPLAY IT TO THE USER
      } else if (data) {
        ....
      }
    });

Note that calling forward will not help as it returns undefined as an argument to then so I wouldn't have nor data nor error and setting errors to null also isn't n option as I actually need to read errors.

@lbrdar Have you found any workaround for this?

Nope 😞

@lbrdar Isn't the way to do this is with a catch ?

 loginMutation({ variables: { data } })
    .then(({ data, errors }) => {
      if (errors) {
        // I WANT TO BE ABLE TO READ ERROR MESSAGE HERE SO I CAN DISPLAY IT TO THE USER
      } else if (data) {
        ....
      }
    }).catch(errors => {

    });

@lbrdar @romucci I am facing the same issue. Did you guys end up resolving this?

@Nosherwan I just installed version 1.1.11 and everything works fine now... You may also check your inner promises (maybe somewhere you forgot to return a promise inside other promise)

@merksam thank you for the comment.
I am using the exact same version as yourself. Unfortunately the behaviour is the same.

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    graphQLErrors.forEach(({ message, locations, path }) => {
        console.log(`[GraphQL error]: ${message}, Location: ${locations}, Path: ${path}`);
      },
    );
  }
});

In the above code block, the graphQLErrors are caught and something can be done in the onError handler. However those errors are not passed to the actual calling promise.
I am using async functions so instead of 'then' I am using await keyword, and that is inside a try catch block.
the catch block catches the error, but there is no error passed to it.

Have someone found any workaround for this?

@Nosherwan @prem-prakash Could we contact somewhere in chat or even make a call so we try to find out why it's working in my case and don't in yours? (if it's still relevant)

I found the solution, kind of weird, but that it is:

This prints just the message

  updateProfile: function() {
      this.$apollo
        .mutate({
          mutation: UPDATE_PROFILE,
          variables: current_user
        })
        .catch((error) => {
          console.log("this prints just the message", error);
        });
    }

BUT this prints the full content of errors

  updateProfile: function() {
      this.$apollo
        .mutate({
          mutation: UPDATE_PROFILE,
          variables: current_user
        })
        .catch(({ graphQLErrors }) => {
          console.log("this prints the full content of 'errors'", graphQLErrors);
        });
    }

I think this issue can be closed
@Nosherwan @romucci @Sceat @lbrdar

what about adding this to the documentation?

Sorry but solution above doesn't work. I guess a full example on how sending back the graphqlErrors in link-error back to the initial caller.

In my case, when there is an error, the following ApolloQueryResult object doesn't get the error details consoled in the onError. Some in the try ... catch surrounding the call. Can not get the graphqlError details from the server. Just a "400 error"..

const gqlResult: ApolloQueryResult<IGReturnData<
            IAllDataTypes
        >> = await apolloClient.query<IGReturnData<IAllDataTypes>, TVariables>({
            query: queryGql,
            variables: queryVariables,
            errorPolicy: "all",
        });

server config:

const errorLink = onError(({ graphQLErrors, networkError }) => {
    if (graphQLErrors)
        graphQLErrors.forEach(({ message, locations, path }) =>
            console.log(
                `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
            ),
        );

    if (networkError) console.log(`[Network error]: ${networkError}`);
});

const httplink = new HttpLink({
    uri: "/graphql",
    credentials: "include",
});

const links = [errorLink, httplink];

export const apolloClient = new ApolloClient({
    link: ApolloLink.from(links),
    cache: new InMemoryCache({ addTypename: false, fragmentMatcher }),
});

@prem-prakash thanks.... savior..

This problem is extremely persistent. I have no control over the graphql server on my project, only the client. The server is responding with graphql errors in the correct shape, but with a 400 status code. In the error-link I have access to graphQLErrors, but in the component mutation, when I pull out graphQLErrors as suggested by @prem-prakash, graphQLErrors is an empty array. I can only access a default message "Error: Network error: Response not successful: Received status code 400". Apollo is clobbering the human-readable error messaging from the server ("user or password incorrect") because the 400 status code is a full-stop for Apollo.

Is there anyone out there who can successfully handle a 400 status code response with error messaging, and pass that messaging to the UI at the component that called the mutation?

Ok well I have a pretty unreasonable approach, but it is working:

I am setting a boolean hasGraphError in the cache, and also caching the error message.

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    graphQLErrors.forEach(error => {
      // log gql error(s)
      console.log("[GraphQL error]: ", error);
      // cache error
      client.writeData({
        data: {
          hasGraphError: true,
          currentGraphError: error.message
        }
      });
    });
  }
  if (networkError) {
    // log network errors
    console.log("[Network error]: ", networkError);
  }
});

Then in a MutationError component, I query the cache for presence of error and the error message, conditionally render the gql error or a real network error:

const HAS_ERROR = gql`
  query IsGraphErrorPresent {
    hasGraphError @client
    currentGraphError @client
  }
`;
export default function MutationError({ error }) {
  const { data } = useQuery(HAS_ERROR);
  const defaultErrorMessage =
    "We're having trouble connecting. Please check your internet connection and try again.";

  // real network error
  if (error && error.message.includes("Failed to fetch")) {
    return <Error>{defaultErrorMessage}</Error>;
  }

  // graph error
  if (error && data && data.hasGraphError) {
    return <Error>{data.currentGraphError}</Error>;
  }

  // probably a real server/network error
  if (error) {
    return <Error>{defaultErrorMessage}</Error>;
  }

  return null;
}

This will be global since I need it on all mutations since my server is _always_ returning 400 for what should be 200 + graphql errors (I'm a little salty about that)...

So the important thing here, is that on every component mutation, I use an empty onError callback which prevents an unhandled exception from Apollo, and on success I must remember to reset the hasGraphError boolean in the cache:

  const [someMutation, { loading, error, client }] = useMutation(SOME_MUTATION, {
    onError() {
      // this callback prevents apollo from throwing
      // ...unhandled exception on 400 status code
    },
    onCompleted({ someMutation }) {
      client.writeData({
        data: {
          hasGraphError: false
        }
      });
    }
  });

Then the mutation error component takes the useMutation error as props (which both allows real network errors to be determined, and makes sure that we are not rendering the global cached gql error on the wrong component):

  {loading && <Spinner />}
  {error && <MutationError error={error} />}

As I said, this approach is pretty unreasonable, however it is currently working to solve:

  1. generally be able to handle graphql errors in the UI even if the response has a non-200 status code
  2. specifically be able to pass errors from apollo-link-error configuration to the calling component, for rendering in the UI

Issues with this approach as coded: this just writes the last GQL error in the array to the cache, so it is not supporting multiple GQL errors at the same time. This should be fairly easy to manage though, by buildling an array of errors, and storing that in the cache, but you'll have to define a local schema/resolvers to do this, or potentially just JSON.stringify it to store in the cache as a string. You'll also need to clear the currentGraphError in the cache on success, rather than just setting the boolean to false.

Hope it helps someone!

Also, in case it helps, note that errorPolicy currently does not work on useMutation hooks, this is a bug, and was recently addressed in this PR: https://github.com/apollographql/apollo-client/pull/5863 , but has not made it to release at this time.

Any progress on this from the developers?

Was this page helpful?
0 / 5 - 0 ratings