Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

apollo-link-error return error to caller #1022

Open
romucci opened this issue Apr 15, 2019 · 17 comments
Open

apollo-link-error return error to caller #1022

romucci opened this issue Apr 15, 2019 · 17 comments

Comments

@romucci
Copy link

romucci commented Apr 15, 2019

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?

@lbrdar
Copy link

lbrdar commented Apr 15, 2019

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.

@romucci
Copy link
Author

romucci commented Apr 18, 2019

@lbrdar Have you found any workaround for this?

@lbrdar
Copy link

lbrdar commented Apr 23, 2019

Nope 😞

@Sceat
Copy link

Sceat commented Apr 28, 2019

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

@Nosherwan
Copy link

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

@merksam
Copy link

merksam commented Jun 18, 2019

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

@Nosherwan
Copy link

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

@prem-prakash
Copy link

Have someone found any workaround for this?

@merksam
Copy link

merksam commented Aug 1, 2019

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

@prem-prakash
Copy link

prem-prakash commented Aug 2, 2019

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

@bastiW
Copy link

bastiW commented Aug 3, 2019

what about adding this to the documentation?

@jeromeSH26
Copy link

jeromeSH26 commented Nov 18, 2019

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

@hmmhmmhm
Copy link

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

@t-lock
Copy link

t-lock commented Mar 5, 2020

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
Copy link

t-lock commented Mar 5, 2020

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
Copy link

t-lock commented Mar 5, 2020

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: apollographql/apollo-client#5863 , but has not made it to release at this time.

@yifengd
Copy link

yifengd commented Apr 12, 2020

Any progress on this from the developers?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests