How to think your application with Relay
Samuel Monteiro
Posted on June 22, 2021
Introduction
First impression
I didn't have a good first impression about Relay when I've started using it. I found it difficult to understand, verbose to use, and I didn't saw the benefits from it.
Even though I didn't like it, I was part of a team, and as a team we choose to stick with Relay and see in the long term if it was a good choice or not.
As the time goes by, I've started to get along with it and to understand how to use it. I still didn't have the full idea, but just to realize how I could use it to solve simple problems like data fetching, and to know what I was doing was enough for me at the time.
Responsibilities
Months later, I was promoted to tech lead and with that came the responsibility to understand and to explain to my team why we're using the stuff that we're using. I had a challenge. I needed to understand why we use Relay and not something else.
And I believe that like any other solution, if you don't know how and why you use it, you're going to face the same or even worse problems that you're trying to solve with it.
This article
This article is a grasp of that process of understanding why we use Relay. I'm going to show you how to think your application using Relay, because I believe that to understand other solutions that Relay provides you need to understand first what problems we have right now.
What's Relay?
It's a JavaScript framework that tries to facilitate the process of fetching data on front-end using GraphQL
. It's developed by Facebook and was conceived with the same ideia of React componentization.
React components and Relay
The ideia behind components in React it's to decrease the complexity of your app by dividing it into smaller parts called components. These components are easier to understand and to maintain, and that increases the capability of your app to scale.
And how does that relates to Relay?
The ideia behind Relay is that you have your data dependencies collocated with your component and that it's beneficial for some reasons:
- It's easier to understand which data is needed for your component to work.
- If your component needs any other data from your server, you don't need to change your entire
query
structure, just your component. (Not every case works this way, but most of them) - It's easier to test your component isolated from your entire structure.
How to use Relay?
To understand it, let's take that YouTube page below:
We can divide it in four components that receive the data from the servers.
-
VideoPlayer
: used to render the video that we're watching. Probably need thevideoSrc
from the server. -
VideoDetails
: show the video details like title, description, author, number of likes and dislikes. -
RelatedVideos
: it's a list of videos that the YouTube algorithm believes that you would like to see. -
UserImg
: renders the logged user profile image.
Just remembering that all of that it's just an example. For sure the real YouTube architecture it's much more complex than this.
With these components in mind, we have two approaches to get the data from the server using Relay.
1. Each component fetches the data that it needs
We can draw a diagram like this to represent that solution:
On the left side, we have a simplified version of the YouTube page. Where each component is represented by a gray circle and they call the server through a GraphQL
query, like this one:
graphql`
query NavbarQuery {
user {
profileImg {
src
}
}
}
`
Benefits
With this solution, we could show different loading indicators in each part of our application. Something like this:
By doing that we improve the user experience by not blocking entirely his access to the screen and show which data we're fetching and what we already fetched.
Here's an article explaining why you should use
loading skeletons
in your interface.
Downsides
The first problem is related with the tree architecture where a component depends on another one to render. As an example, let's get just the structure responsible for showing us the video:
Here, we're only going to get the data with the videoSrc
when the component VideoPlayer
is fully rendered. And if for some reason any of these components above the VideoPlayer
take a long time to load, we would need to wait that time until we can call the server and start loading the video.
With that we would have two times to load the video:
- rendering the components above the
VideoPlayer
. - recieving the
response
from theserver
with thevideoSrc
data.
Another problem is that we would end up with a lot of requests to the server, where each one of them will ask just for a single part of the data. And it makes sense that once the connection is open with the server, we ask for all the data that we need.
Ok, so we have these two problems. What it's the alternative?
2. The recommended solution
Instead of fetching the data on each component, we fetch them once on the page load, in other words, every page is a query.
But didn't you said that Relay is built on colocating the data dependencies with the components that need them?
Yes, I did. When I said data dependencies
I didn't mean the fetch function, I mean the declaration of the data that it's needed. We only fetch once, at the page render. It would look like this
Benefits
With that, we ask for all the data that we need to render the page on the page load. This comes with the following benefits:
- We decrease the amount of request made to the server.
- Since we're not waiting for some component to load, we decrease the load time to show relevant data to the user.
Colocating the data dependencies with Fragments
In order to colocate the data dependencies of a component close to it, we can use Relay
Fragments
.
A Fragment
, in Relay
, it's a declaration of the data that a specific component need.
It's like what we had with every component making a fetch, but instead of a fetch, we're only declaring the data that we need, and the fetch only occurs once. The implementation would look like this:
// page component
graphql`
query PageQuery {
user {
...NavbarFragment_user
}
}
`
// navbar component
graphql`
fragment NavbarFragment_user on UserType {
profileImg {
src
}
}
`
This way, the Navbar
declares exactly what it needs and if something change, we will only change on the fragment, not on the page query.
Even though this is nice, we have a problem :(
Downsides
At the version 10
of Relay
we can't have a loading indicator
per component, we need to show a loading indicator
on the entire page before showing some data to the user, something like this:
You have two ways to solve this problem.
You could use the first approach to solve this problem, where each component calls a fetch
and while this fetch
don't return a response, you show a loading indicator
.
The other way and the one that I recommend, is to upgrade your Relay to version 11 and start to use the @defer
directive from GraphQL
alongside Suspense
component from React
.
With the @defer
directive you could say that a specific piece of your query
, like a fragment
, should be loaded asynchronously and while the response of that piece doesn't return from the server, you show a loading indicator
passed to your Suspense
component.
Here's the Relay documentation where they explain in more details how to do that and in this video how they're using that on Facebook.
Conclusion
Just as React
once was, Relay
still a not much used library and because of that there's not much articles and tutorials explaining how it works.
I do hope that this article increased your understanding about how to use Relay
on your application or how its main idea works.
If you missed a more technical explanation, or if you still have some questions that I didn't cover, feel free to send me a tweet or a DM 🤙
Posted on June 22, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.