How to think your application with Relay

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 the videoSrc 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 the server with the videoSrc 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 šŸ¤™

26