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

getDataFromTree throws errors when any query fails #615

Closed
choonkending opened this issue Apr 10, 2017 · 48 comments · Fixed by #2753
Closed

getDataFromTree throws errors when any query fails #615

choonkending opened this issue Apr 10, 2017 · 48 comments · Fixed by #2753
Assignees

Comments

@choonkending
Copy link

choonkending commented Apr 10, 2017

Background

I asked a question on StackOverflow today here.

I thunk about it harder afterwards and decided that this is a discussion worthy of a Git issue.

Problem

Any error gets thrown in getDataFromTree.

Expected/Proposed Behaviour

Return the data with an error Array, without throwing an error.

Why?

Our GraphQL server speaks to multiple APIs and returns a graphQL response which is handled by Apollo UI.

Certain GraphQL errors are not considered critical, and we want to still render a page with it. E.g.

data: {
   thing: {
       id: "1234"
       name: "Thing"
       nonCriticalData: null
   },
   error: {
       graphQLErrors: [
           path: ['thing', 'nonCriticalData']
       ]
   }

With the above response we get from apollo, we want to still render a page with thing.

I have followed #488 (comment) and got to this point.

In my mind the errors should be returned and not thrown unless necessary, because not all errors are equal.

Ideally I would like to do the following

getDataFromTree(app)
    .then(() => {
             const content = ReactDOMServer.renderToString(app);
             const { data } = client.getInitialState();   <------ Data (with errors)
             // check for expected errors and render
             return renderPage(data);
     })
    .catch(error => {
         // handle unexpected errors
    });

Version

  • apollo-client@1.0.0-rc2
  • react-apollo@1.0.1

P.S. Thanks for an awesome library. Happy to help out if needed.

@helfer
Copy link
Contributor

helfer commented Apr 21, 2017

Hey @choonkending, sorry it took me so long to get around this issue. I agree with you 100% that this behavior isn't consistent, but because of the way server side rendering is implemented, we can't easily change it to do what you want. The reason is that the queries are all executed in a first pass on the route, then their data is stored in the cache. When you call renderToStringWithData, the data from the cache is used to render the tree synchronously. We don't cache errors, so there's currently no easy way to change this without either changing the way server side rendering works (make it asynchronous), or changing the way errors are handled in the store. Even if the rendering were identical on the server and the client given the same query, the initial react render on the client would be unaware of the errors (because they're not stored), so there would be an inconsistency there. 🙁

We're planning improvements in the next version of the API that would make this problem go away, but for the moment I think it's quite difficult to change.

If you're very interested and motivated to work on this problem, just let me know, we could definitely use the help!

@choonkending
Copy link
Author

@helfer No worries! Thanks for getting back to us.

If you're very interested and motivated to work on this problem, just let me know, we could definitely use the help!

Definitely.

Just to get a better understanding:

  • How does the caching model work? Can you point us to parts of the codebase where you think this is the tricky part to change?
  • Are there any docs/git issues around the thinking for the next version of the API?

@helfer
Copy link
Contributor

helfer commented Apr 21, 2017

@choonkending the cache normalizes all data, and when you run a query against the cache, the result is assembled from the normalized parts. That's a problem if errors don't have paths, because then there's no way to know which field an error is associated with, and no way to know if an error should be returned with the query run against the cache.

There's no discussion issue yet for the next version of the API, but I'm trying to put an initial post together soon. In the meantime, I think the best thing to do would be to write a use-case for each requirement that we can think of. Having SSR be consistent with client rendering is one use-case, and I think we should write that down and document it well, so we can have a discussion around it.

For that purpose, could you add a comment here, please? #519

You should also feel free to write down other use-cases that haven't been mentioned yet (so far there are very few, I'm hoping people will add more to it, even current functionality of react-apollo that they are relying on).

@jbaxleyiii
Copy link
Contributor

This really needs to be talked through and fixed 💪 After I clean up the tests and add better typings. I'll try to get around to error handling and SSR!

@Dattaya
Copy link

Dattaya commented Jun 30, 2017

Just want to leave a comment in support of @choonkending proposal. I already handle errors in components that perform queries: if {props.data.error} return a component that shows the error's message (I'm talking about errors that are thrown from resolvers/connectors). So I expect the server to render the exact result that would be rendered on the client. I tried to use a workaround suggested in #488 (comment) but it doesn't really work, because apolloClient.getInitialState() returns a data object without an error.

@OllieJennings
Copy link

OllieJennings commented Jul 4, 2017

I would just like to say that this has become a big issue for me.

As my app has gotten more complex, i have gone down the route of having 2 bundles, and if a 404 then rendering 2nd bundle, and losing the SPA benefits.

If you could still keep query errors in state, and allow SSR of 404 pages, it would simply things massively

@edoroshenko
Copy link

Hi @helfer

If you're very interested and motivated to work on this problem, just let me know, we could definitely use the help!

I'm very interested and motivated (as long as it's my impediment).
Could you let me know if you already started approaching this problem? If no, do you have some thoughts how you would approach it?

@helfer
Copy link
Contributor

helfer commented Jul 17, 2017

Hi @edoroshenko. That's awesome. @jbaxleyiii can probably tell you more about it since he designed it together with Tom and is now working on Apollo full time!

@edoroshenko
Copy link

Thanks @helfer!
Do you guys have a slack channel or what is the best way to communicate?

@jbaxleyiii
Copy link
Contributor

@edoroshenko you bet! Check out http://dev.apollodata.com/community/ and join us on slack! I'd be happy to work together on finally bringing some better error support to Apollo and SSR in general!

@jbaxleyiii jbaxleyiii self-assigned this Jul 17, 2017
@edoroshenko
Copy link

The way I see the solution:

  1. Start storing GraphQL errors in the store
  2. Stop throwing GraphQL errors from apollo client
  3. Introduce error-policy option, that would define what to do with errors in the cache. It could be either refetch or reuse. refetch is a default one and should work as it works now. So apollo should always do query even if there's an error in the cache. reuse should tell apollo client to respond with an error if it has one in the cache.
  4. In graphql high order component we should always use reuse error policy first time and refetch on any subsequent request. This way we'll be able to transfer the state with errors from server to client and perform first client-side rendering based on it without extra queries to the server.

This plan will affect apollo-client and react-apollo repos.

@jbaxleyiii , @helfer, what do you think about this plan?

Or do you have another ideas, how to approach this issue?

@stale
Copy link

stale bot commented Aug 10, 2017

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!

@stale
Copy link

stale bot commented Aug 24, 2017

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!

@ghostganz
Copy link

This is still a huge issue for us, how do I re-open it?

We've wasted a lot of time trying to figure out why our error handling doesn't work on SSR while it works fine in the browser. It should be documented somewhere that it isn't expected to work yet!

@t0x1c123
Copy link

Same problem here. Any news? For example i want to throw error with info when user is not authenticated. This doesn't work with SSR getDataFromTree 😢

@OllieJennings
Copy link

@jbaxleyiii any news on this since apollo 2.0 is coming, would be nice to see something like this sorted

@gabriel-miranda
Copy link

This is still happening and it kinda breaks SSR.
when query works:
getDataFromTree => data = {loading: false, error: false, <key>: ...}
when query fails:
getDataFromTree => error thrown and all data discarded {error}
This makes that after SSR there is no error, loading is set to true and every <key> needed is empty.
I think it should work kinda like this
getDataFromTree => data = {loading: false, error: true, <key>: ...}
Because if it doesnt, the whole app lose consistency.

@edorivai
Copy link
Contributor

edorivai commented Feb 2, 2018

@jbaxleyiii Can this issue please be re-opened?

@tspecht
Copy link

tspecht commented Feb 21, 2018

Any news on this? Having the same issue with react-apollo 2.0

@choonkending
Copy link
Author

choonkending commented Feb 21, 2018

Hey peeps, just giving an update of the approach we've taken in my team since this issue was created. Hopefully it helps if it applies to your use case!

We opted to rely less on react-apollo when it came to data fetching. (I'm not bagging the good work that's been done on react-apollo! It's just the way we decided to go from a technical perspective)

  1. Use ApolloClient.query in ApolloClient instead
  • This works if you have your query defined beforehand
    👍 You can fetch data first before rendering the app altogether
    👍 You can decouple your data fetching from your view altogether. Keeping them separate helped us reason about the data flow better.
  1. Use errorPolicy: all in errorPolicies
    👍 Returns data + errors

You should be able to handle server side rendering with just apollo client!

I hope this helps!

@capjamesg
Copy link

This issue should be reopened. I am using a similar setup as described in the question and every time there is an error, the .catch() statement logs the error to the console and stops the entire page from rendering. Any help would be appreciated.

@mhaagens
Copy link

mhaagens commented Nov 4, 2018

Here's one approach to dealing with this. Just ignore errors on the server, but make sure you handle them in your components;

  // Ignore errors
  const errorLink = onError(({ graphQLErrors, networkError, response, operation, forward }) => {
    return forward(operation);
  });

  // Create Apollo client and link to schema
  const client = new ApolloClient({
    ssrMode: true,
    cache,
    link: ApolloLink.from([
      errorLink,
      stateLink,
      new SchemaLink({
        schema,
        context: { req, res },
        rootValue: {
          currentUser: req.user ? req.user : null
        }
      })
    ])
  });

@richardscarrott
Copy link

@mhaagens but how do you handle the errors in your components?

@mhaagens
Copy link

mhaagens commented Nov 6, 2018

If the app errors on the server I add the error to apollo local state in a second pass from a try/catch handler and then render a generic error component in the app. I can then pick up that error state on the client as well. If it errors on the client I render the same component with the client side error instead.

Basic flow;

  1. Try to render app
  2. App errors - catch it in a try/catch
  3. Run a second pass with the error passed in to local state
  4. Render error component in app

Now make sure you don't follow this example to the letter in production, you don't want to leak those errors to the client, but you can use the same idea.

SSR Route handler;

export default async (req, res) => {
  try {
    // Render response from React app
    const response = await createResponse(req, res);
    res.send(response);
  } catch (e) {
    try {
      // If errored run second pass to get React app error response
      const serverContext = { error: JSON.stringify(e) };
      const response = await createResponse(req, res, serverContext);
      res.send(response);
    } catch (e) {
      // Couldn't render error response with React
      res.send("Something went very very wrong. Failed to catch error.");
    }
  }
};

Top level app component;

{this.state.errors || this.props.clientState.serverError ? (
    <ServerError clientErrors={this.state.errors} serverError={this.props.clientState.serverError} />
) : <App />}

@osamu38
Copy link

osamu38 commented Dec 4, 2018

any update?

@daryn-k

This comment has been minimized.

@yaroslav-ilin
Copy link

Please, reopen the issue.

@hwillson
Copy link
Member

Wow - sorry, this issue should have been re-opened a while ago 😳. I'll digest everything here and hash out a plan of attack based on how getDataFromTree works now (since as of react-apollo 2.3.0 it's using ReactDOM.renderToStaticMarkup to be compatible with React Hooks). I'll post back with the details, and if anyone is interested in working on this, that would be awesome!

@hwillson hwillson reopened this Jan 24, 2019
@hwillson hwillson assigned hwillson and unassigned jbaxleyiii Jan 24, 2019
@OllieJennings
Copy link

Thanks a lot @hwillson, Apollo is great, this is just the last pain point for me right now

@yaroslav-ilin
Copy link

Was this issue closed by an accident or is it expected?

@hwillson
Copy link
Member

hwillson commented Feb 1, 2019

@nilfalse Closed intentionally - the changes in #2753 should fix this. We'll be pushing out a new version with these changes next week.

@Dattaya
Copy link

Dattaya commented Feb 1, 2019

We'll be pushing out a new version with these changes next week.

Wasn't #2753 published in 2.4.1?

@gbiryukov
Copy link

gbiryukov commented Feb 21, 2019

network errors thrown from apollo-link-rest@0.7.0 still causes getDataFromTree to throw in 2.4.1. Just wondering does apollo-link-rest throws errors incorrectly or is there any other issue?

@OllieJennings
Copy link

@gbiryukov i believe the fix is only for GraphQL errors, as Network errors are realistically a different thing all together and shouldn't be swallowed.

@gbiryukov
Copy link

aah, the issue with this idea is that it works different on client.

when network error occurs in browser – component receives error prop.
when network error occurs on server getDataFromTree just throws.

this makes impossible to handle errors consistently on both environments

@OllieJennings
Copy link

Well there is a lot of debate around this, but i think the current solution of network errors throwing and graphql errors being passed to props is correct in my opinion.

We have full consistency with graphql errors on both environments, and that's the important part, because that part is what we realistically control, we don't control the network (if you get what l mean).

My solution is if l hit a network error, refresh the page. My server should then throw an error if it still is a network error, so l can redirect to say a status page.

@kaanozcan
Copy link

kaanozcan commented Sep 30, 2019

react-apollo version: 3.1.1 I can confirm using ApolloError while using renderToStringWithData or getDataFromTree just throws it and doesn't pass down the error as intended. I am rerendering without apollo on catch statement of renderToStringWithData and letting the client redo the request.

@OllieJennings
Copy link

@kaanozcan do you have errorPolicy: all set?

@kaanozcan
Copy link

That indeed works. Thanks @OllieJennings

@IAmNatch
Copy link

IAmNatch commented Feb 26, 2020

I was stuck on this forever. It seems possible that if you're using useQuery hooks that there are better way to handles this. Since the project I'm working on was built before hooks was introduced, I had to look for an alternative method.

The problem seemed to stem from rendering Apollo providers with a null Apollo state. It seemed to get stuck in an endless loop, thus the unresponsive server.

To get around this, I evaluated if the apolloState was empty or not. If it was in fact empty, that pointed to an Error in getDataFromTree.

From there, knowing I had an error, I'd render my app (_app) with an error prop passed in. The error flag stops the ApolloProviders from rendering, and the app from rendering, and instead returns an ErrorComponent. This has stopped network errors from bombing my server.

It's not pretty, and I don't love it, but it means that the server won't require a manual restart in the event of a backend outage, and that I can render a consistent error message. I believe it won't automatically resolve on a resumed connection + navigation to another page, because the client does not have any other routes in this. This means you'd need to refresh the browser to clear the error.

I'll post some samples below in case it will help anyone, and I'm open to additional opinions!

// withApollo.js
....
render() {
      const isApolloEmpty = Object.keys(this.props.apolloState).length
			return (
				<App
					{...this.props}
					error={!isApolloEmpty}
					apolloClient={this.apolloClient}
				/>
			);
		}
...
//_app.js
render() {
		const { Component, pageProps, apolloClient, error } = this.props;
		if (!error) {
			return (
				<Container>
					<ApolloProvider client={apolloClient}>
						<ThemeProvider theme={theme}>
							<AppWrapper>
								<Component {...pageProps} />
							</AppWrapper>
						</ThemeProvider>
					</ApolloProvider>
				</Container>
			);
		} else {
			return (
				<Container>
					<ThemeProvider theme={theme}>
						<Error statusCode={"A Network Error has Occured"} />
					</ThemeProvider>
				</Container>
			);
		}
	}

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

Successfully merging a pull request may close this issue.