Caching fields on relay modern with hooks

Helielson
Jusbrasil Tech
Published in
5 min readOct 24, 2019

Giving context

I’ve been working with Relay since the beginning of 2016, enough time to affirm that it’s a really great framework for building and maintaining data-driven React applications. Relay is a client to GraphQL and in this post I’m assuming that you are familiar with all these technologies: React, Relay and GraphQL. If you aren’t, there is a lot of resources and articles that can help you:

Since 2016, the Relay Classic version framework was completely rewritten and Relay became Relay Modern. A lot of improvements were done in the new version and with it one thing was gone: the “automatic” cache of Relay Store. In the classic version, the cache between queries was automatic, which means that if a component requested a field from a GraphQL type already requested by another component, the field value was returned from the Store and no requests were sent to the GraphQL Server.

To illustrate it, imagine you have 2 screens: ScreenA and ScreenB. Both are connected to Relay in order to retrieve the same data through a graphql query, something like:

Using the default implementation of Relay, if ScreenB is mounted after ScreenA, Relay will request both queries to the GraphQL server even if the data is already available in the Relay Store (because it was requested while mounting ScreenA). It happens because Relay lets each user configure the network layer according to their needs, by offering you all the tools you need and letting the configuration up to you.

Also, data will be loaded again from GraphQL server for every mounting of these screens. Visually, if had you defined some kind of loading component on your QueryRenderer, this loading would appear every time you mount the screen.

RelayQueryResponseCache

In order to avoid unnecessary calls on GraphQL server there is a way to store the Relay Query Response with RelayQueryResponseCache. It allows us to cache data using queryID and query variables as cache key. The article below explains how to configure it with the Relay Environment:

As you can see in the post above, it uses the RelayQueryResponseCache implementation, which caches data based on queryID. But what does it mean? It means that if different screens request the same data, both requests will still be sent to the GraphQL Server, because the query names are different. Even though the requested fields are the same, the query name is different: ScreenAQuery != ScreenBQuery and Relay compiler forces us to use the module name in the query name.

So, using the example above with the ScreenA and ScreenB, after configuring an instance of QueryResponseCache, data will be requested once for each screen. It’s still not a good implementation. Also, the UX is degraded because the user sees a loading while the data that was already requested before is being requested again.

The solution

Given this problem, let’s see how to implement a cache based on query fields with relay hooks. The relay@7.0.0 already has the code necessary to implement it in a package called relay-experimental that holds all the “non-official” code of the framework, although this code is already being used internally at Facebook.

useLazyLoadQuery hook

Along with other hooks in the experimental package, there is the useLazyLoadQuery hook. It allows us to lazy load data with a defined fetchPolicy. Fetch policies define how data will be resolved to the renderer. There are 4 policy types available now:

  • store-only: Considers only Relay Store (local) as source of truth. Here any data is fetched from GraphQL Server, so you need to fill the Store by yourself;
  • store-or-network: Considers both Relay Store (local) and network as source of truth. If the Store doesn't satisfy, then Relay fetches data on GraphQL Server;
  • store-and-network: Considers both Relay Store (local) and network as source of truth. Independently of the Store satisfying the query Relay fetches data on GraphQL Server
  • network-only: Considers only the GraphQL Server as source of truth. All requests are fetched from GraphQL Server

To resolve the field cache problem we will use the store-or-network policy.

In order to use any Relay hook, the component using it needs to be wrapped by a RelayEnvironmentProvider

Now, whenever ScreenA or ScreenB are rendered, the Relay DataChecker will lookup for each field value from the query on Relay Store first, and then request the missed fields to the GraphQL server. So, if the Relay Store satisfies the query then the component will be rendered with no loading, because no requests will be sent to the server, Otherwise, the GraphQL server will be requested only with the missing fields.

code from Relay Facebook

BONUS: MissingFieldHandlers

Handling cached data from connection nodes

If you are implementing a list of items and you are using a query to define the View component (eg.: eventById) probably you'll have cache problems. To illustrate it, consider the following query:

The events query

and the following GraphQL response:

the stored data for this response on Relay has an event ID! (that generally is the field name defined on GraphQL schema) as key, along with its arguments encoded as base64. Something like this:

After listing the events, we want to show the event details with another screen using another query:

Even if you are using the useLazyLoadQuery hook on both components (listing and view), Relay won’t recover data from cache because the key used to check the store will be formatted in the format: base64Encode(‘event(id: 1)’) and this key doesn’t exist on Relay Store.

A workaround to it is to define a custom MissingFieldHandler:

and pass it to the Environment along with the RelayDefaultMissingFieldHandlers:

The final result is a full cached query. This way the application will request only the missing fields. Here is the result of this caching approach implemented on Jusbrasil Escritório Online app:
Note that the only part loading when accessing the view is the content in the center.

Conclusion

As I said, Relay is such an amazing tool and it’s getting better day by day. The Modern version brings performance improvements and a code aligned with the latest React changes, like hooks.

Remember that we are using an experimental package, so there is no guarantee it’ll keep the same in the future. The API can change, but probably the core idea will remain.

If you want to know more about Relay I recommend that you read the docs. If you need any help, drop me a line on twitter @helielsonst.

Do you want to work with Relay, GraphQL, React, React Native and a lot of other fun technologies? Join us, we are hiring at Jusbrasil 🙂

--

--

Helielson
Jusbrasil Tech

In love with music, javascript, golang and a lot of others things related to technology. Software Engineer at Jusbrasil