Hey all, we have another guest post, this one comes from Sibelius Seraphini - a very active contributor to Relay and its eco-system. When we spotted he had wrote an amazing article on how the networking aspects of Relay comes together, we wanted to expand his reach and inform more people on how Relay comes together.
Data fetching is a hard problem for apps. You need to ask yourself a lot of questions: How do you ask for data from a server? How do you handle authentication? When is the right time to request data? How can you ensure you have all the necessary data to render your views? How can you make sure you're not over-fetching? Can you do lazy loading? When should you trigger lazy loading of data? What about pre-fetching data?
You don’t deep dive if you don’t know how to swim
TL;DR Relay Modern Network
Relay will aggregate the data requirements (fragments) for your components, then create a request to fulfill it. The API to do this is via the Relay Environment:
The Relay "Environment" bundles together the configuration, cache storage, and network-handling that Relay needs in order to operate.
This post focuses on the "network-handling" part, the Network Layer. The network layer's responsibility is to make a request to a server (or a local graphql) and return the response data to Relay. Your implementation should conform to either FetchFunction for a Promise-like API, or SubscribeFunction for an Observable-like API.
This article will provide 5 implementations of a Relay Network Interface, each of one providing more capabilities than the other one, eventually enabling GraphQL Live Queries and Deferrable Queries.
You can see the code for these 5 network layers on GitHub here, open source under MIT license: https://github.com/sibelius/relay-modern-network-deep-dive.
Simplest Network Layer
The simplest network layer would; get the request, send it to a GraphQL server to resolve and return the data to Relay environment.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
Network that Handle Uploadables
The GraphQL spec does not handle form data, and so if you need to send along files to upload to your server with a mutation, you'll want to use the uploadables API in Relay when you commit the mutation.
Adding uploadables in a mutation will inevitably get passed to your network interface, where you'll need to change your request body to use FormData instead of the JSON string above:
1 2 3 4 5 6 7 8 9 10 11 12 13
Network that Caches Requests
This builds on top of the other 2 implementations, we use RelayQueryResponseCache to query GraphQL requests based on query and variables.
Every time a mutation happens, we should invalidate our cache as we are not sure how a change can affect all cached query responses.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
Network using Observable
Relay provides a limited implementation of the upcoming ESObservables spec. I recommend reading A General Theory of Reactivity to understand why Observables are a great solution instead of promises in some situations. Notably; a promise is one value in a time space, an observable is a stream of values in a time space.
To work with this API, we're going to use a private interface for the observable object called Sink:
1 2 3 4 5 6 7 8 9 10 11
Which is the shape of the Observable object we pass back to Relay:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
This is an implementation you would need when working with GraphQL Live Queries (based on polling), as you are going to resolve the same query more than once.
Deferrable Queries Network
A common case for deferrable queries is to lazy load fragments. This lets you get request content above the page fold first, and then request additional data after. A good example is loading a Post's content first and then subsequently loading all comments of this post after the post has finished.
Without deferrable queries you could simulate this using the @include directive in your Relay fragment
and a refetch container. When the component mounts the refetch container changes the variable used on
@include to true and it will request the rest of the data.
The problem with above approach is that you need to wait for the component to mount before you can start the next request. This becomes a bigger problem as React does more work asynchronously.
An ideal deferrable query will start as soon as the previous query has finished, rather than depending on your
React components render cycles. Relay provides a directive for this:
1 2 3 4 5 6 7 8 9
In the fragment above, Relay will first get the
commentsCount from the Post, then afterwards Relay
will get the data for
CommentsList_post fragment. Sending both through the observable.
Here is the implementation of an execute function to handle a batched request:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
This execute function now can handle 2 types of requests:
- a single GraphQL query
- or a
BatchRequestthat could have be many queries with inter-related data
So, what does the
batchRequestQuery function look like?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
Relay Modern is very flexible
Depending on your application needs, you can scale from a simpler Promise-based API for your custom network layer to one that uses Observables to always resolves from cache data first and then resolves from the server.
Here are some production examples:
ReactRelayNetworkModern: A network layer that uses the middleware pattern to separate responsibilities like retrying, logging, caching and auth.
timobetina's example: The simplest Observable network layer you can start with.
If you want to expand your understanding of GraphQL and Relay Modern, I have two great related resources:
A boilerplate that uses dataloader to batch and cache requests to your database in a GraphQL API: https://github.com/entria/graphql-dataloader-boilerplate
A simple boilerplate for working with Relay Modern and React Navigation: https://github.com/entria/ReactNavigationRelayModern
If you have questions about this or anything send me a DM on twitter https://twitter.com/sseraphini