React-apollo: State empty when SSR fails because of getDataFromTree throws

Created on 12 Dec 2017  ·  15Comments  ·  Source: apollographql/react-apollo

Intended outcome:
SSR query fails and fills the field loading with: false, error with: <error> and the <keys> you wanted with their respective values. And doesn't run again on client side.

Actual outcome:
SSR query fails and fills the field loading with: true, error with: undefined and the <keys> you wanted with undefined.

How to reproduce the issue:
You can reproduce it here: https://github.com/gabriel-miranda/chorus/tree/develop querying a post that doesnt exist, or doing a query that fails in any server with SSR (next.js example also).
This happens because when you do:

    try {
          // Run all GraphQL queries
          await getDataFromTree(
            <ApolloProvider client={apollo}>
              <ComposedComponent url={url} {...composedInitialProps} />
            </ApolloProvider>
          );
        } catch (error) {
          // Prevent Apollo Client GraphQL errors from crashing SSR.
          // Handle them in components via the data.error prop:
          // http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error
        }

File reference: https://github.com/gabriel-miranda/chorus/blob/develop/src/www/utils/withData.js

getDataFromTree throws an error and doesn't fill the apollo state needed.

Version

Most helpful comment

any ideas?

All 15 comments

Any updates on this? Should I send a PR fixing this after I find out the best solution?
The issue is here: https://github.com/apollographql/react-apollo/blob/c95f7cbe5d08c69c4be457dffa2e641dbc1e7c57/src/getDataFromTree.ts#L178

This breaks entirely SSR

What's even weirder, if you are using the next.js with-apollo example and upgrade to the latest preset, if the GraphQL server returns an error status code (like 400), the try catch does successfully catch the getDataFromTree successfully, but somehow the error is thrown later, after the try catch and the render, causing a crash :/

@flipxfx have you tried using getDataFromTree().catch(/* your callback goes here. */)

I using try { await getDataFromTree(...) } catch (error) {}. It catches the error there, but then throws it again somewhere along the way.

I using try { await getDataFromTree(...) } catch (error) {}. It catches the error there, but then throws it again somewhere along the way.

Maybe use a try/catch block within the catch callback to figure out what's happening.

Well now I can't reproduce it. I've updated quite a bit on our Apollo server and client since then so not sure what was happening.

@gabriel-miranda did you find a solution to this? Currently the only solution I can see is to handle errors differently on the server vs client, e.g.

const ProductDetailPage = () => {
    <Query query={PRODUCT_DETAIL_PAGE}>
        {({ loading, error, data }) => {
            if (loading) return 'loading...';
            // Errors are handled here, but only for the client render
            if (error) return <Status code={error.code}>{error.code === 404 ? 'Not found' : 'Server error}</Status>; 
            if (data) return <Product {...data.product} /> 
            return null;
        }}
    </Query>
}

const serverRenderer = async (req, res) => {
    const routerContext = {};
    const root = ...;
    try {
        await getDataFromTree(root);
    } catch(error) {
        // Errors have to be handled here for the server render (which a) is a different code path to the client and b) is not component specific)
        res.status(error.code).send(renderToString(<Status code={error.code}>{error.code === 404 ? 'Not found' : 'Server error}>);
        return;
    }
    const html = renderToString(root);
    res.status(routerContext.status).send(html);
}

I get a similar issue when using renderToStringWithData.
Ended up with a similar workaround to just print the error:

    res.status(500)
    res.end(
      `An error occurred with the following stack trace:\n\n${error.stack}`
    )

getDataFromTree has been re-written since this issue was created, so please try out a current day version of react-apollo and let us know if this problem is still happening. Thanks!

@hwillson This is still an issue with the latest version.
I use

    "@apollo/react-hooks": "3.1.0",
    "@apollo/react-ssr": "3.1.0",
    "apollo": "2.18.1",
    "apollo-cache-inmemory": "1.6.3",
    "apollo-client": "2.6.4",
    "apollo-link-http": "1.5.16",

Here is an example. We want to get the current user name and handle the case that the user is not logged in (which results in a GraphQl error):

const CurrentUserQuery = gql`
  query CurrentUser {
    currentUser {
      name
    }
  }
`

function useCurrentUser() {
  return useQuery(CurrentUserQuery, {
    ssr: true,
    errorPolicy: 'none', // 'all' somewhat helps, at least the end result is the same, but I want the error object!
  })
}


function ApolloTest(props) {
  const { data, loading, error } = useCurrentUser()

  if (error) {
    return <BaseText>ERROR: {error.message}</BaseText>
  }

  return (
    <BaseText>
      {loading ? 'LOADING...' : (data && JSON.stringify(data)) || 'NO DATA'}
    </BaseText>
  )
}

The server returns "LOADING...." while the client will show the error message after hydration.
The server throws the following exception:
Error while running `getDataFromTree` { Error: GraphQL error: Not logged in at new ApolloError (./node_modules/apollo-client/bundle.umd.js:92:26)

I think this is a duplicate of https://github.com/apollographql/apollo-client/issues/3897

@hwillson This is still an issue, even with 4.0.0!
Even worse, after getDataFromTree handled an error once, every following SSR request will break. This is only fixed by restarting the server!
It seems that there is an unintended side effect during SSR rendering which persists between queries.

I tested with Next.js 9.2.1:
Throw an error on page 1,
open another page 2 (directly in the browser, meaning via SSR) which uses Apollo and the page will return as if the page would be loading (I disabled JS). However when the client runs it has a fully filled cache and renders as if the page was successfully loaded.
Because of the way Reacts hydration works, this can lead to the weirdest rendering! For me a big banner at the top is cut in half after SSR because CSS styles get applied to the wrong elements.

    "@apollo/client": "3.0.0-beta.34",
    "@apollo/react-ssr": "4.0.0-beta.1",
   "apollo-cache-inmemory": "^1.6.3",
    "apollo-link-http": "^1.5.16",
    "apollo-link-retry": "^2.2.15",

any ideas?

@hwillson we are having the same issue. When an SSR throws an error it will throw the error on our page level and have to restart the server. I see getDataFromTree is rewritten (but not catching) and we are using version 3 latest.

how to proceed? The error will not be in the return, the app will just break

When errorPolicy is set to all it will not throw it during treewalker, but error is not getting passed to component.

Created reproduction on codesandbox in the issue mentioned below #3918

Same issue. We have authenticated queries that are allowed to fail which return loading: false, error: ... when they do. getDataFromTree will fail if the user is not logged but keeps the loading set to true for the query even though the query has failed, preventing our logic from displaying the proper UI.

Was this page helpful?
0 / 5 - 0 ratings