apollo-link icon indicating copy to clipboard operation
apollo-link copied to clipboard

apollo-link-error return error to caller

Open romucci opened this issue 5 years ago • 17 comments

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?

romucci avatar Apr 15 '19 05:04 romucci

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 avatar Apr 15 '19 19:04 lbrdar

@lbrdar Have you found any workaround for this?

romucci avatar Apr 18 '19 22:04 romucci

Nope 😞

lbrdar avatar Apr 23 '19 11:04 lbrdar

@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 => {
		
	});

Sceat avatar Apr 28 '19 19:04 Sceat

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

Nosherwan avatar Jun 16 '19 23:06 Nosherwan

@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 avatar Jun 18 '19 13:06 merksam

@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.

Nosherwan avatar Jun 19 '19 02:06 Nosherwan

Have someone found any workaround for this?

prem-prakash avatar Jul 31 '19 14:07 prem-prakash

@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)

merksam avatar Aug 01 '19 14:08 merksam

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

prem-prakash avatar Aug 02 '19 18:08 prem-prakash

what about adding this to the documentation?

bastiW avatar Aug 03 '19 13:08 bastiW

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

jeromeSH26 avatar Nov 18 '19 19:11 jeromeSH26

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

hmmhmmhm avatar Jan 28 '20 00:01 hmmhmmhm

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?

t-lock avatar Mar 05 '20 07:03 t-lock

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!

t-lock avatar Mar 05 '20 13:03 t-lock

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.

t-lock avatar Mar 05 '20 13:03 t-lock

Any progress on this from the developers?

yifengd avatar Apr 12 '20 19:04 yifengd