apollo-link-error: Can this do anything besides print to console?

Created on 27 Mar 2018  路  7Comments  路  Source: apollographql/apollo-link

Expected Behavior

I would expect to be able to write the error to the cache, but there seems to be something in the chain of things (maybe because this is declared before the client constructor???) is not letting a write to the cache persist and as there are no examples outside of just printing errors to the console, I am unsure of what can be done...

Actual Behavior

A write to the cache using cache.writeData({ data }) is successful and then things are mysterisouly rewritten to defaults after a pollInterval is set on a query...

A _simple_ reproduction

https://github.com/deltaskelta/apollo-link-iss-573

Issue Labels

  • [x] has-reproduction
  • [ ] feature
  • [ ] docs
  • [ ] blocking
  • [ ] good first issue

has-reproduction

Most helpful comment

This isn't really an answer to the issue you encountered, but why do you need to write the errors to your global state?

I have a separate notifications system to handle things like error notifications. It might work well for you

My client setup solution looks like this

const App = props => (
  <NotificationSystem>
    {({ addNotification }) => (
      <Authentication
        onError={message => addNotification({ message, level: 'error' })}
        authURL={this.props.authURL}
      >
        {({ clearToken, token, username }) => (
          <ReduxStoreProvider
            reduxReducers={this.props.reduxReducers}
            addNotification={addNotification}
          >
            <ApolloClientProvider
              graphqlURL={this.props.graphqlURL}
              authenticationURL={this.props.authURL}
              subscriptionURL={this.props.subscriptionURL}
              addNotification={addNotification}
              authToken={token}
              deauthenticate={clearToken}
            >
              {this.props.children({
                addNotification,
                username,
                logout: clearToken,
                onError: message => addNotification({ message, level: 'error' }),
              })}
            </ApolloClientProvider>
          </ReduxStoreProvider>
        )}
      </Authentication>
    )}
  </NotificationSystem>
)

where my ApolloClientProvider looks like

export class ApolloClientProvider extends React.Component<Props> {
  private _apolloClient: ApolloClient<any>; // tslint:disable-line:no-any

  public componentWillMount() {
    /** Configure Apollo Client */
    // Create an http link:
    const httpLink = new HttpLink({
      uri: this.props.graphqlURL,
      fetchOptions: {
        agent: new Agent({
          rejectUnauthorized: false,
        }),
      },
    });

    // Create error handler link
    const errorLink = onError(({ graphQLErrors, networkError }) => {
      if (graphQLErrors) {
        graphQLErrors.map(({ message, locations, path }) => {
          this.props.addNotification({
            title: 'GraphQL Error',
            message: `Message: ${message}, Location: ${locations}, Path: ${path}`,
            level: 'error',
          });
        });
      }

      if (networkError) {
        this.props.addNotification({
          title: 'Network Error',
          message: `${networkError}`,
          level: 'error',
        });
      }
    });


    const networkLink = this.props.subscriptionURL != undefined ? split(
      // Split based on operation type
      ({ query }) => {
        const { kind, operation } = getMainDefinition(query) as OperationDefinitionNode;
        return kind === 'OperationDefinition' && operation === 'subscription';
      },
      new WebSocketLink({
        uri: this.props.subscriptionURL,
        options: {
          reconnect: true,
        },
      }),
      httpLink,
    ) : httpLink;

    const authAfterware = configureAuthAfterware({ deauthenticate: this.props.deauthenticate });
    const authMiddleware = configureAuthMiddleware({ authToken: () => this.props.authToken });

    const apolloLink = from([
      authMiddleware,
      errorLink,
      networkLink,
    ] as any);// tslint:disable-line:no-any

    this._apolloClient = new ApolloClient({
      connectToDevTools: true,
      link: authAfterware.concat(apolloLink as any) as any,// tslint:disable-line:no-any
      cache: new InMemoryCache(),
    });
  }

  public render() {
    return (
      <ApolloProvider client={this._apolloClient}>
        {this.props.children}
      </ApolloProvider>
    );
  }
}

I know this doesn't really answer the question, but it might be a serviceable work around for you

All 7 comments

EDIT: just added the reproduction of the issue, anyone able to tell what is going on here?

This isn't really an answer to the issue you encountered, but why do you need to write the errors to your global state?

I have a separate notifications system to handle things like error notifications. It might work well for you

My client setup solution looks like this

const App = props => (
  <NotificationSystem>
    {({ addNotification }) => (
      <Authentication
        onError={message => addNotification({ message, level: 'error' })}
        authURL={this.props.authURL}
      >
        {({ clearToken, token, username }) => (
          <ReduxStoreProvider
            reduxReducers={this.props.reduxReducers}
            addNotification={addNotification}
          >
            <ApolloClientProvider
              graphqlURL={this.props.graphqlURL}
              authenticationURL={this.props.authURL}
              subscriptionURL={this.props.subscriptionURL}
              addNotification={addNotification}
              authToken={token}
              deauthenticate={clearToken}
            >
              {this.props.children({
                addNotification,
                username,
                logout: clearToken,
                onError: message => addNotification({ message, level: 'error' }),
              })}
            </ApolloClientProvider>
          </ReduxStoreProvider>
        )}
      </Authentication>
    )}
  </NotificationSystem>
)

where my ApolloClientProvider looks like

export class ApolloClientProvider extends React.Component<Props> {
  private _apolloClient: ApolloClient<any>; // tslint:disable-line:no-any

  public componentWillMount() {
    /** Configure Apollo Client */
    // Create an http link:
    const httpLink = new HttpLink({
      uri: this.props.graphqlURL,
      fetchOptions: {
        agent: new Agent({
          rejectUnauthorized: false,
        }),
      },
    });

    // Create error handler link
    const errorLink = onError(({ graphQLErrors, networkError }) => {
      if (graphQLErrors) {
        graphQLErrors.map(({ message, locations, path }) => {
          this.props.addNotification({
            title: 'GraphQL Error',
            message: `Message: ${message}, Location: ${locations}, Path: ${path}`,
            level: 'error',
          });
        });
      }

      if (networkError) {
        this.props.addNotification({
          title: 'Network Error',
          message: `${networkError}`,
          level: 'error',
        });
      }
    });


    const networkLink = this.props.subscriptionURL != undefined ? split(
      // Split based on operation type
      ({ query }) => {
        const { kind, operation } = getMainDefinition(query) as OperationDefinitionNode;
        return kind === 'OperationDefinition' && operation === 'subscription';
      },
      new WebSocketLink({
        uri: this.props.subscriptionURL,
        options: {
          reconnect: true,
        },
      }),
      httpLink,
    ) : httpLink;

    const authAfterware = configureAuthAfterware({ deauthenticate: this.props.deauthenticate });
    const authMiddleware = configureAuthMiddleware({ authToken: () => this.props.authToken });

    const apolloLink = from([
      authMiddleware,
      errorLink,
      networkLink,
    ] as any);// tslint:disable-line:no-any

    this._apolloClient = new ApolloClient({
      connectToDevTools: true,
      link: authAfterware.concat(apolloLink as any) as any,// tslint:disable-line:no-any
      cache: new InMemoryCache(),
    });
  }

  public render() {
    return (
      <ApolloProvider client={this._apolloClient}>
        {this.props.children}
      </ApolloProvider>
    );
  }
}

I know this doesn't really answer the question, but it might be a serviceable work around for you

Yeah...a workaround like that could be done, but I kind of got into this local state thing from the article that made it sound like it could be used like redux so that is how I am trying to use it. I just don't want the added confusion of having to pass the addNotification prop down to every little component that can run a query and possibly error....

For example, how would you get at the NotificationSystem from deep inside the Apps children?

So, it appears this may be an issue with not setting __typename and/or id in the local state but I cannot be sure yet. If I put an object into the local state, does it have to have a typename and id as if it were a regular cache object? In the todos example there is a visibilityFilter which does not have a __typename but that is just a one off string and not a regular object....

If I leave out the __typename I get some errors, but what if I want a one-off object just like the string? Is that possible, or have I misunderstood something?

Yes. It definitely needs a typename and id because that is how the inMemoryCache normalizes data. Not sure why the cache doesn't throw though

@dyst5422 what do you mean by cache doesnt throw?

There is a lot going on here that is unintuitive...

  1. A top level simple key like visibilityFilter in the todos example works.

  2. An object mutation followed by a query works, unless a poll interval is set

If this is meant to replace redux then I should be able to put whatever arbitrary global state required into the cache

To anyone who may stumble here, it appears I misunderstood how the local state works. pollInterval is meant only for network requests.

If you are using local state, the Apollo Client and the cache should take care of updating your queries to the local state so you should never have to poll it.

Was this page helpful?
0 / 5 - 0 ratings