React-apollo: Unhandled Network Error

Created on 7 Apr 2017  Â·  80Comments  Â·  Source: apollographql/react-apollo

Steps to Reproduce

Describe how to reproduce this issue.

  1. Being offline or using an invalid url
  2. Start your app

Buggy Behavior

Unhandled promise rejection stops the execution of the app.

Expected Behavior

Component should render without data and errors received through props. The same when its a query error or some kind of http error.

Version

Most helpful comment

@7ynk3r and everyone watching this issue!

This one is tricky and to rip the bandaid off, the current behavior won't be changed in the 2.* line. I'm very open to changing at some point, but with the introduction of Query components (which will have its own renderError method) I think this can be avoided easily with that upcoming solution (it will be on the 2.* line too!)

That being said, I wanted to elaborate on why it is like it is currently, as well as provide a few workarounds that work today.

Why throw

I'm a hard believer that errors should be handled in UI, or at least in code to give feedback of some kind to a user. With this in mind, having the network error attached but never used felt like it would be too easy to build apps that didn't show the error. This was before my previous company had typechecking setup so it was easy for product devs to write UI and never handle possible errors.

With react 16, the throwing behavoir fits inline quite nicely with the new componentDidCatch method. This encourages handling of errors in subtrees which is always great practice.

This library was written before react-native really was a big concern and I do see how their behavoir to break the app on a single thrown error is problematic! Its frustrating to see that decision, but I don't see that changing. That is one of the reasons why the additive upcomming releases will support a new component API which is more tolerant for errors, though I do encourage everyone to handle them in components.

Solutions

  • @kkemple laid out a great HOC based solution here
  • @apski added one using a link which will handle it across your UI here
  • @timbotnik mentioned using componentDidCatch which is a great way to handle subtree errors! In fact, a lot of the test of this library use it just for that!
  • @Yamilquery added a great solution with using erroPolicies here
  • You can even set the errorPolicy for all components using the defaultOptions key when creating your apollo client instance like here

Summary

All in all, I hear you and I'm sorry this is frustrating and especially for the lack of formal response. That is on me, but its hard to keep up with all of the issues. Luckily a few incredible people have stepped up to help maintain and push forward this project!

This will be easier with upcomming releases, can be solved now in a variety of ways, and is by design. I'm sorry if that isn't what you wanted to hear :( I really really am! Its one of those designs that won't make everyone happy.

Thank you all for the help and use of this project though. Seeing this much activity is so amazing and keeps us working to document, write, and build things better to make it easier to use GraphQL in your app!

Thanks!
James

All 80 comments

@maxtechera thanks! Could you provide more information, like a stack trace or a reproduction using react-apollo-error-template? That would help us fix the issue faster.

I have the same issue when the url is invalid.

screen shot 2017-04-07 at 3 42 53 pm

Closing since this has been longer than 2 weeks without a reproduction. If it is still an issue, feel free to reopen!

This happens for me too. All you have to do is kill your graphql server and try running a query in react native. It will throw an unhandled error. I was trying to mimic the server being down and see how I was supposed to handle errors. Not sure how this isn't an issue for others?

Yeah same for me ! I have absolutely no idea how to handle down server. When I open my app in Airplane mode it's just crashing...

How are we supposed to catch exceptions from this? Any ideas @jbaxleyiii

Same for me. In my case, I attach a jwt token in header for apollo client. Whenever the jwt token is expired, server side return error json with status equal to 401, and I got 'Unhandled (in react-apollo)', 'ApolloError .

I solved this by handling exception on main page where the apollo-query has been executed.

if(this.props.data.error)
  return <ErrorComponent /> ;

This is horribly repetitious. I don't want to have to do that. I want to catch all these in my top level component and throw up an alert view, otherwise every render method I have to do this. Or make a higher order component in front of the component making the query.

Hello,

Here is a repo to reproduce the error https://github.com/sabativi/apollo-offline-network-error
I use https://mpjk0plp9.lp.gql.zone/graphql as graphql url

Also to look at the problem here is a video record : http://recordit.co/DrPPhE4Fof.

Hope it will help @helfer

Hey, I had posted a repo to reproduce more than one week ago and still do not have any feedback.
Did i miss something ?

Hello,
I have a same problem

Hi @sabativi sorry I didn't respond earlier, I don't always have time to check issues every day.

Thanks a lot for the reproduction, it helped me quickly figure out what is going on. In essence, refetch works a little bit differently from other queries, and it's up to you to explicitly handle errors. That means you should change your code from simply refetch() to refetch().catch(e => { /* handle error here */). If you don't want to handle the error, you can use catch( e => null).

Here's a complete example:

class App extends Component {
  render() {
    const { data: { refetch, loading, hero, error } } = this.props;
    if(loading) {
      return (<p>Loading…</p>);
    }
    return (
      <main>
        <header>
          <h1>Apollo Client Error Template</h1>
          <p>{error && error.message}</p>
          <h3>{!error && hero.name}</h3>
          <button onClick={() => refetch().catch(e => null)}>
            Refetch
          </button>
        </header>
      </main>
    );
  }
}

I hope it helps! 🙂

PS: I'm happy to discuss changing this behavior in a future major version of the API, but for the time being we have to keep it because it would be a breaking change to remove it.

@helfer This still is not a solution to the problem. In my render method, if I do this:

let { vaults, refetch } = this.props.data;

And an error occurs, a big red box shows up saying unhandled network error. If I simply change it to destructure and pull out the error, it works:

let { vaults, refetch, error } = this.props.data;

This is pointless, because either way, I have after middleware catching all errors at the network interface level. Apollo should not be telling me the exception is uncaught when I am logging it in the network interface middleware. As I mentioned earlier, myself and many others do not want to add error handling on every component. Without this being fixed, we have to manually check for an error inside every component that makes a query.

@helfer You stated, that one needs to handle the error when it should not be thrown. Is there a possibility to do so when using the react-apollo graphql(query).catch(?!) object?

Because one can't catch on the component.

Indeed this is the problem I was trying to show by using a refetch when offline, but obviously was not a good choice...

same here. the call works in GraphiQL but not using react-apollo & apollo-client.

anyone know how to solve it?

Hi, has this problem been solved?
I'm wondering the same thing as @nenti .

All my containers are wrapped in graphql(query)(component) and call the query everytime they're rendered.
Is there a way to catch these Network error?
@helfer

Hey @vincentpageau, I'll let @jbaxleyiii give you the authoritative answer on that, but I believe the way to "handle" the error when using the graphql HOC is to simply check data.errors inside the component. There's a check inside apollo-client that throws if data.errors isn't accessed.

@helfer I would love to get rid of that check! Haha

@helfer @jbaxleyiii So basically when the loading value switches to false, check if data.error has a value?
Would it

  • catch the error?
  • crash before it enters the if(data.error) { ... } _loop_
  • enter the _loop_ and still crash?

I'm using this at the moment and the Network error still crashes my app:

const {error, loading} = this.props.data;

    if (loading) {
      return null;
    }else if (error) {
      return (
        <div className='ComponentDistance'>
          <Translate value='application.error_message' />
          {error}
        </div>
      );
    } else {
    return <div> ... </div>
    }

Thank you for your patience^

I could make a global error handler thanks to apollo-fetch afterware.

import { all } from 'ramda'
export default ({response, options}, next) => {
  //response.raw will be a non-null string
  //response.parsed may be a FetchResult or undefined

  if (!response.parsed || !response.parsed.errors) {
    next()
    return
  }

  const errors = response.parsed.errors.map(error => {
    switch (error.operationName) {
      case 'someQueryOrMutationKey':
        // error handling code...
        next()
        return
      default:
        return null
    }
  })

  if (all(error => error === null, errors)) {
    delete response.parsed.errors
  }

  next()
}

You could also check for response.status like here https://github.com/apollographql/apollo-fetch#error-handling

Apollo Integration

@helfer This is not a solution to the problem. In my render method, if I do this:
{ data:{refetch} } = this.props;

Is there a way to catch these "ExceptionsManager.js:71 Unhandled error Network error: Network request failed Error: Network error: Network request failed"?

I am kind of pulling my hear over this – please help. I am inspecting data.error in my component, which shows up as undefined, yet I am getting an unhandled

error Network error: Network request failed with status 500 - "Internal Server Error" Error: Network error: Network request failed with status 500 - "Internal Server Error"

in the console. I'm on [email protected] and [email protected]

Short update: in my case I'm seeing undefined because I also have a Redux reducer listening for APOLLO_QUERY_ERROR action type, and I am guessing that "touches" the data.error which subsequently gets reset. Can that be?

@pierlo-upitup I had a similar issue that was caused by redux-persist. Blacklisting the apollo reducer solved it for me.

Not using redux-persist, but thanks for the idea @Deevent

UPDATE: disabling SSR (and server store rehydration) clears the problem described above. So thanks for the hint, it was very close :) Ignore my comments.

UPDATE 2: Actually I was mistaken – that does not seem to be the issue.

Can we get rid of this line and just pass along errors? https://github.com/apollographql/react-apollo/blob/master/src/graphql.tsx#L515 Sorry if this is incorrect, I quickly jumped into the code.

Experiencing this issue as well. Rather a strange architecture decision to throw error if not accessed

@julianeon 100% agreed. I wish someone would reply that can give us a reason why this can't be removed.

Perhaps a new solution coming from the Error Boundary pattern in React 16? https://facebook.github.io/react/blog/2017/07/26/error-handling-in-react-16.html

Although I do agree that boundary error catching is an elegant solution, I still don't think that apollo network errors should throw.

In my opinion they should just set the error prop and allow the component to render (or not render) error related UI accordingly

edited to remove confusion

I keep my ears wide opened to a suggestion / fix for this error.

As of now, we have to check for errors in each component that has a query?

Would be awesome to throw an error or toggle redux to show the connection was lost:

https://support.toggl.com/wp-content/uploads/2013/02/Screen-Shot-2017-02-13-at-22.20.23.png

I'm failing to see why this can't be solved with a HOC in user land.

Turning the error prop into a boolean is no good because some may want to use error information in app. (I return different error types for different issues, i.e. authentication/permissions/bad request/etc)

With a simple higher order component and using compose you can handle all errors with very little ceremony.

const catchError = options => WrappedComponent => {
  // do things with options if you need more flexibility
  return class NetworkErrorHandler extends Component {
    componentDidMount = () => {
      if (this.props.data && this.props.data.error) {
        // do something with error
      }
    };

    componentWillReceiveProps = nextProps => {
      if (nextProps.data && nextProps.data.error) {
        // do something with error
      }
    };

    render = () => <WrappedComponent {...this.props} />;
  };
};

compose(graphql(SOME_QUERY), catchError())(ComponentToWrap);

Turning the error prop into a boolean is no good because some may want to use error information in app. (I return different error types for different issues, i.e. authentication/permissions/bad request/etc)

Agreed, in my previous comment what I was trying to say was that apollo should continue to set the value of the error prop (not necessarily to true) but without throwing. The issue here is not the value of the error prop, but the fact that it gets thrown.

With a simple higher order component and using compose you can handle all errors with very little ceremony.

The ceremony comes in where you have to explicitly attach that HOC to every component that makes use of Apollo.

IMO, a jumpy network should not (by default) crash an application - which is what's currently happening.

This issue has been automatically labled because it has not had recent activity. If you have not received a response from anyone, please mention the repository maintainer (most likely @jbaxleyiii). It will be closed if no further activity occurs. Thank you for your contributions to React Apollo!

pinging to not close

@julienvincent I am currently working on a similar case, and using afterware solved the case. It works like middleware, but for responses instead of requests.

This issue has been automatically labled because it has not had recent activity. If you have not received a response from anyone, please mention the repository maintainer (most likely @jbaxleyiii). It will be closed if no further activity occurs. Thank you for your contributions to React Apollo!

This issue has been automatically closed because it has not had recent activity after being marked as no recent activyt. If you belive this issue is still a problem or should be reopened, please reopen it! Thank you for your contributions to React Apollo!

No update about this ? I'd prefer to avoid users to see a red screen just because they have a bad connection

Maybe it could be handled in the "onError" of "apollo-link-error" ?

same, how to handle this ?

Same issue. It's been going on for a really long time. Why does the error get thrown at all?

Still no solution to catch the errors?

Edit:

I found the solution: (errorPolicy: 'ignore')

graphql(MyQuery, {
    options: ({ resultQuery: { me } }) => (
      {
        errorPolicy: 'ignore',
        variables: {
          customer_id: (me && me.id) ? Number(me.id) : 0,
        },
      }
    ),
    name: 'result',
  }),

The same issue here. Any solutions using onError in apollo-link-error?

Same issue here, looking for solution.

ApolloLink onError handling from https://github.com/apollographql/apollo-link/issues/207

import { ApolloClient } from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { createHttpLink } from 'apollo-link-http';
import { onError } from 'apollo-link-error';
import { InMemoryCache } from 'apollo-cache-inmemory';

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

const httpLink = createHttpLink({ ... });

const link = ApolloLink.from([
  ...otherLinksIfNeeded,
  errorLink,
  httpLink,
]);

const client = new ApolloClient({
  link,
  cache: new InMemoryCache(),
  queryDeduplication: true,
});

Still waiting for the Apollo team to remove the error if we don't access the props.data.error.

@7ynk3r and everyone watching this issue!

This one is tricky and to rip the bandaid off, the current behavior won't be changed in the 2.* line. I'm very open to changing at some point, but with the introduction of Query components (which will have its own renderError method) I think this can be avoided easily with that upcoming solution (it will be on the 2.* line too!)

That being said, I wanted to elaborate on why it is like it is currently, as well as provide a few workarounds that work today.

Why throw

I'm a hard believer that errors should be handled in UI, or at least in code to give feedback of some kind to a user. With this in mind, having the network error attached but never used felt like it would be too easy to build apps that didn't show the error. This was before my previous company had typechecking setup so it was easy for product devs to write UI and never handle possible errors.

With react 16, the throwing behavoir fits inline quite nicely with the new componentDidCatch method. This encourages handling of errors in subtrees which is always great practice.

This library was written before react-native really was a big concern and I do see how their behavoir to break the app on a single thrown error is problematic! Its frustrating to see that decision, but I don't see that changing. That is one of the reasons why the additive upcomming releases will support a new component API which is more tolerant for errors, though I do encourage everyone to handle them in components.

Solutions

  • @kkemple laid out a great HOC based solution here
  • @apski added one using a link which will handle it across your UI here
  • @timbotnik mentioned using componentDidCatch which is a great way to handle subtree errors! In fact, a lot of the test of this library use it just for that!
  • @Yamilquery added a great solution with using erroPolicies here
  • You can even set the errorPolicy for all components using the defaultOptions key when creating your apollo client instance like here

Summary

All in all, I hear you and I'm sorry this is frustrating and especially for the lack of formal response. That is on me, but its hard to keep up with all of the issues. Luckily a few incredible people have stepped up to help maintain and push forward this project!

This will be easier with upcomming releases, can be solved now in a variety of ways, and is by design. I'm sorry if that isn't what you wanted to hear :( I really really am! Its one of those designs that won't make everyone happy.

Thank you all for the help and use of this project though. Seeing this much activity is so amazing and keeps us working to document, write, and build things better to make it easier to use GraphQL in your app!

Thanks!
James

It could be me but it looks like its throws every time if you have a named query too. i.e. https://www.apollographql.com/docs/react/basics/queries.html#graphql-name

@pennyandsean Could you provide a minimal reproduction case?

Ok, I hope this helps. https://codesandbox.io/s/k098ol1ymr
It contrasts the two APIs. The graphql HOC throws after consecutive bad requests every time, even if you do check the error. It looks like the new render-prop <Query/> API works ok. Well, it doesn't throw even if you don't check the error, that's fine.

Update: actually I can still make it throw with the new API if it give it a flakey enough network.

Thanks for all the offered solutions. I'm wondering if there's an easier way to catch the error at the highest level (the component, say, where the client is being created) and display an alternate component with a warning message and a "retry" button. No problem displaying that component upon catching the error, but I can't seem to get the ApolloProvider element to actually retry connecting (it just keeps trying to fetch forever). I'd rather not go into my individual components and make them all refetch (or whatever is necessary) since there are MANY of them.

This is bs.
Unhandled (in react-apollo:Apollo(ValidateToken)) Error: Network error: Response not successful: Received status code 401
Cool, where to catch it? Nobody knows...

Jesus, is there no way around this?

I don't think Jesus is among us

Setting error policies on the client solved it for me.

const apolloClient = new ApolloClient({
 ...
  defaultOptions: {
    watchQuery: {
      errorPolicy: 'none'
    },
    query: {
      errorPolicy: 'none'
    },
    mutate: {
      errorPolicy: 'none'
    }
  }
})

Thank you @gastonmorixe, I'm gonna try that out — setting it on each query, even if accessing this.props.data.errors, did nothing at all on ReactNative, and still kept crashing with a red screen. This throwing is kind of a nightmare on React Native right now

@Amnesthesia I am with you there, it is hell on RN. Good luck.

Hey @mjasnikovs 👋, were you able to properly catch 401 exceptions? I'm already accessing props.data and catching errors from .refetch(), but errors keep being thrown... 😓

@diegocouto I ran on a similar issue while using apollo-link. Where I would try catching an error by all means and was still getting the red screen. The root cause was a sort of uncatchable error that was being raised.

Take a look at this issue: https://github.com/apollographql/apollo-link/issues/542, might help you in some way...

@diegocouto Hi, enjoy this pain. Apollo products like trowing errors randomly by design. So enjoy this ride.

I made this weird looking component. It is working for me. :/

import React from 'react'
import PropTypes from 'prop-types'
import {Message} from 'semantic-ui-react'

const Errors = ({errors}) => {
    if (!errors) {
        return null
    } else if (Array.isArray(errors.graphQLErrors) && errors.graphQLErrors.length) {
        return <Message negative>
            <Message.Header>Kļūda</Message.Header>
            <p>{errors.graphQLErrors.map(val => val.message).join(<br />)}</p>
        </Message>
    } else if (errors.message) {
        return <Message negative>
            <Message.Header>Kļūda</Message.Header>
            <p>{errors.message}</p>
        </Message>
    }
    return null
}

Errors.propTypes = {
    errors: PropTypes.oneOfType([PropTypes.object, PropTypes.bool])
}

export default Errors

Then

<Query query={DRAWINGS_CATS_Q}>
  {queryState => {
    if (queryState.error) {
      return <Errors errors={queryState.error} />
    }

@gastonmorixe @Amnesthesia @jbaxleyiii What is the solution to stop this error in react native, even when accessing the error and console.logging it is still being thrown globally. I can't catch it with componentDidCatch either nor does an errorLink help.

I respect that the opinion is that errors should be handled explicitly, but this is not documented and the solution is unclear and it crashes React Native apps easily, something should be done about this. At least a proper documentation, or better an option to switch it off via a flag in the client.

Do I have to switch to the new Query component to prevent this @pennyandsean ? I'm still using graphql and would like to keep it that way, I guess I could easily build my own graphql HOC.

Seems that replacing the graphql component by a custom HOC that uses Query under the hood, solved the issue. This urgently needs better documentation though. I'd write some, but I'm not sure I understand how network errors are actually handled.

In react native using the Query component i only have this error when doing a refetch or fetchMore. If it's just the normal query, it handles everything fine.

I tried all the solutions excluding using a HOC, nothing worked for refetch or fetchMore.

@MrLoh, in the end I could still make it fail with the render props, just not as often. My project is react native so it was particularly visible, however in the codesandbox sample, it helps to open up the console to see the exceptions. Looking at the code, both APIs wrap the basic query so you could roll your own. Must admit I haven't tried it with the basic client.query yet.

It seems to come from here is that helps anyone. I could be wrong but maybe @stubailo was that your baby? Perhaps refetch and fetchMore need to be brought under the errorPolicy?

I'm having the same issue here, no way of catching the network errors with Apollo. Every time I switch off the internet connection I get the red screen error no matter which solution I try. @MrLoh can you share the HOC you created? I'd love to test if it works for me too.

+1. I'm not getting a red screen, but even after accessing props.data.error in a component wrapped in the HOC component 'graphql' I'm getting a "Possible Unhandled Promise Rejection: Error: Network error: Network request failed" in React Native

@404sand808s I'm also looking for a easier way to retry (all kinds of) operation, did you found any?

@rankun203 sadly no, not yet.

An irrelevant comment has been moved to stack overflow.

@rankun203 this seems to have nothing to do with this issue. Please don’t pollute issues with random related questions, but use stack overflow.

@MrLoh That's reasonable, sorry for putting wrong discussions here.

My issue is when I navigate to a route, at this moment token expired, my graphql server will throw an 'Unauthorized' error.

At apollo client, with apollo-link-error, the error will be caught. But it will broadcast to current component with graphql() enhancer. The app will crash.

It does't make sense that handle the global error(Unauthorized) in local (in Component)

I want to intercept the error and stop mount react component.

function errorHandler({ graphQLErrors, networkError }) {
    if (graphQLErrors) {
      graphQLErrors.map(error => {
        if (error.code === 1001) {
          auth.signout();
          window.location.replace('#/login');
          //TODO: stop here!! Don't mount component and navigate to '#/login' route
        }
      });
    }

    if (networkError) {
      if (networkError.statusCode === 401) {
        auth.signout();
        window.location.replace('#/login');
      }
    }
  }

So if you're offline, component blows up in ReactNative, it does latch the error to the error prop afterwards. My setup is pretty default, using apollo-link-error as well

image

@immortalx i remember reading that if you're refetch is failing then you need to latch on catch at the end of that refetch().catch((e) => {});

edit: Turned out deconstructing was the issue, oops.

<Error style={styles.errorStyles} {...error} />

There is another solution that involves accessing the error for any query/mutation by wrapping the graphql decorator/function. I don't guarantee this solution to be perfect. But this is what I came up with when we couldn't move to a newer version of react-apollo:

export const graphql = (document, operationOptions) => {
  const origHof = origGraphql(document, operationOptions)
  return (WrappedComponent) => {
    const CustomGraphqlWrapper = (props) => {
      let handler = null
      if (
        (!operationOptions || !(handler = props[operationOptions.name])) // if named handler not defined continue checking
        && !(handler = props.data) // if unamed query handler not defined keep checking
        && !(handler = props.mutate) // if unamed mutation handler not defined then we know not try accessing an error
      ) {
        return origHof // we aren't decorating the wrapped component with a handler, so no error needs accessing
      }
      if (handler.error) {
        console.log('GraphQL Request Error', handler.error)
      }
      return <>
        <WrappedComponent {...props} />
      </>
    }
    return origHof(CustomGraphqlWrapper)
  }
}
Was this page helpful?
0 / 5 - 0 ratings