relay: GraphQL error handling in Relay Modern
I think error handling is a little bit weird.
Contra the example in https://facebook.github.io/relay/docs/network-layer.html, it looks like I need to actually throw
an Error
in the event that there are any query errors. I can’t just pass through the graphql
object with the errors
property.
This is because in https://github.com/facebook/relay/blob/v1.1.0/packages/relay-runtime/network/RelayNetwork.js#L197-L208, normalizePayload
only throws an error if data
is nully.
But the forward throw
branch there is the only code path that will trigger onError
for the request, and correspondingly the only path that will hit onError
in <QueryRenderer>
per https://github.com/facebook/relay/blob/v1.1.0/packages/react-relay/modern/ReactRelayQueryRenderer.js#L205-L212.
Otherwise we hit onNext
per https://github.com/facebook/relay/blob/v1.1.0/packages/react-relay/modern/ReactRelayQueryRenderer.js#L229, and will always set error: null
on the readyState
that gets passed to the render
callback.
This is weird. Am I missing something, or are things supposed to work this way?
About this issue
- Original URL
- State: open
- Created 7 years ago
- Reactions: 61
- Comments: 39 (14 by maintainers)
Commits related to this issue
- Handle partial GraphQL errors Simple workaround for Relay bug (see: https://github.com/facebook/relay/issues/1913). I have no idea how to write tests for this part of application. I would prefer to m... — committed to kiwicom/mobile by mrtnzlml 7 years ago
- Handle partial GraphQL errors Simple workaround for Relay bug (see: https://github.com/facebook/relay/issues/1913). I have no idea how to write tests for this part of application. I would prefer to m... — committed to kiwicom/mobile by mrtnzlml 7 years ago
- Handle partial GraphQL errors Simple workaround for Relay bug (see: https://github.com/facebook/relay/issues/1913). I have no idea how to write tests for this part of application. I would prefer to m... — committed to kiwicom/mobile by mrtnzlml 7 years ago
Seems like there’s a lot of confusion on what @taion is talking about in this thread.
I think I’m hitting the same issue @taion is and wanted to illustrate with some examples. Basically, I’m passing GraphQL errors back to Relay and they’re getting swallowed by QueryRenderer. Something like:
Then in the render function of
QueryRenderer
I get props like{ viewer: {me: null }}
and error isnull
. However, I am expecting to get back the GraphQL errors I passed back.For the
nully
data returning a processed version of the error that @taion mentions, if I return:from the server, then I get my error to come through but it gets formatted and aggregated into some Relay Modern error:
So it seems like I can access my errors if I force my data to be
nully
as @taion mentioned but that definitely feels a bit weird. Also, the GraphQL spec says errors can be returned even when some data can be returned. In their example, one of the elements is missing aname
property and an error is returned andname
is set to null, but the rest of the graph is returned. http://facebook.github.io/graphql/#sec-ErrorsNot quite the issue – the problem is actually in QueryRenderer.
For me setting
data
tonull
was not an option, because I needed the partial data and handle the errors somewhere in my components as well. I found a workaround to be able to get partial errors inside my query payload in Relay containers. I basically wanted to be able to create a query like followingThis way the partial errors are accesible inside my components. To accomplish this I created a new error type in the GraphQL schema. I use
graphql-js
here.I added an
errors
field on the root query type.The
errors
field does not have a resolver. Soerrors
is alwaysnull
in the GraphQL response payload. The trick is to fill theerrors
inside thefetcher
as follows.You can query the
errors
field now in any component. For testing you can add a type like following to your schema, where one field cannot be resolved due to some error.If you want to handle server errors directly in your component you can add an
error
field to the corresponding type as above. We can use thefetcher
again to set the error on this type. Following function is not well tested, but does the job for now.You can call it in the fetchers fetch function like following
Still trying to figure out how to handle “network is unavailable” in Relay without it crashing in production - I have the error in fetchQuery but don’t know what to do to get it back to the Component to display. Is everyone here stuck on the same thing or can someone point me to a resource?
I have somewhat older version of Relay Modern (v.1.6.0). This is how I fixed the issue.
When graphql sends error message, it has a default format like following,
You can modify it to simple format by adding
(for more info on how to add custom error message, go here: https://medium.com/@estrada9166/return-custom-errors-with-status-code-on-graphql-45fca360852)
formatError
After doing this, Now graphql will send error message in following format. Now
errors
is array of string.Now go to your front-end side code, open
environment.js
(file in which relay environment is setup)add these lines to returned
fetch
function offetchQuery
function.Now, It will kind of look like this:
Note:- Don’t catch this error. This thrown error will get transmitted to QueryRenderer where it will be available as
error
prop. handle it there (show error related UI if error is present).Please fix this, this is annoying!
@renatonmendes Thank you for your answer. This is my code:
But i have on error on TypeError: errors.map is not a function (like if errors would be an array and not object, but if i put that object in array is not a very clean solution)
Yup, I love that post. It has a great breakdown of these problems. “very manual process” definitely was meant to encompass the work involved around setting up all the types/interfaces/unions required to represent these errors, but even after you’ve implemented most of the pieces in that blog post, without a lot of additional work, in most backend frameworks I’ve seen, its still relatively easy to end up with errors in your root level errors object. If you follow the advice in that blog post and have confidence most errors will come through as typed data in your response, you can always use one of the strategies mentioned above to bubble any remaining errors up to the root and have it fail at the query level. Every framework I’ve worked with (but I haven’t looked at too many yet) default to having errors in a resolver will automatically fall back to making the closest nullable parent field null and inserting the error into the root errors list.
For most applications I’ve worked in, developers try to handle the obvious error cases like a failed request, but there are almost always cases that are not explicitly handled, and explicitly handling every possible error case is often not practical.
I am not trying to suggest that using unstructured errors like this is a good pattern, just that is a pattern which is common, and even in backends that try to implement better error handling by baking it into the schema are still likely to have some errors fall through the cracks and fall back into the root errors array. In these cases, I would personally prefer to have a way to extract those errors when retrieving the data/fields which the errors correspond to, rather than being forced to handle them at the root level.
I have been wondering if there would be a way to add support for additional patterns around error handling by adding a few new APIs.
At a high level, the issue seems to be that relay does not have a way to represent non-critical errors, or errors which are not intended to bubble all the way to the root of the query without introducing/representing those errors at the schema level. While I understand the value and benefits of properly representing error states you want to handle in your schema, and treating them as data rather than exceptions, many backend frameworks and patterns have been built around the idea of partial responses, and allowing errors in nullable fields to automatically be captured in the root error array with a
path
, and letting the rest of the response to succeed. In most of the backend frameworks for graphql I’ve looked at, automatically creating intermediate objects or unions for representing various error states is a very manual process, and I have not seen good examples of patterns that make handing things like failed network requests to an external service easy to handle without a lot of manual error handling and custom types.I think eventually there will be some new backend patterns that will make this easier, but the current state of the world seems to be that there are lots of real world examples of graphql schemas that use patterns with partial + errors in responses.
Here is my ideal solution:
getValue
that looks for errors associated with the field being retrieved and can return/throw those errors.The end result of this would be that you could create error boundaries around components that consume a fragment and handle errors for more granular parts of a query without having to break down your query into smaller requests to get more graceful degradation.
I am pretty new to relay, so my understanding of relays concepts are pretty rough, but I think adding something like this could be done in a way that adds minimal overhead to existing patterns and would create something that is completely opt-in, while still providing a solution to a large set of problems around error handling.
I think the issue there is that it assumes that a null means there was an error, which is often not the case, and would effectively mean you your schema can either have valid nulls or errors, but not both.
It seems like you are suggesting that there is are patterns you can use in your schema/server to represent any error condition in you schema. I don’t disagree, but I also don’t think it is a cleaner solution. Having every field wrapped in a union makes your queries and you schema a lot more complicated, many schemas already exist, and use the simpler pattern of nulls + top level errors, and migrating these schemas to properly represent all their error cases in the schema might be a lot more complicated than exposing a way to access these errors in relay.
I was not trying to advocate for this pattern to be the preferred way to handle errors in relay, just hoping it would be possible to expose these errors for users who would like to be able to access them somewhere outside of the root level query or the network layer.
But, after thinking about the problem a little more, I think all this is actually pretty manageable without anything in relay itself. It should be pretty straight forward to basically map errors to object ids + fields at the network layer, then write some hooks that wrap the relay hooks to check for errors when unmasking a fragment.
I decided to go other way - I dont think
commitMutation
should reject at all (because “partial success” feature is one of the founding principles of GraphQL)@nodkz https://github.com/relay-tools/react-relay-network-layer/pull/61 - Check it out and let me know what you think
Leaving a link to @josephsavona’s explanation here for posterity.