Skip to content

Commit

Permalink
Add blog post explaining how Relay provides unique value in data loading
Browse files Browse the repository at this point in the history
Reviewed By: voideanvalue, alunyov

Differential Revision: D50606876

fbshipit-source-id: f7552c4b012ec69cd382c2f8ec5d7e8de888b981
  • Loading branch information
captbaritone authored and facebook-github-bot committed Oct 24, 2023
1 parent a2cf506 commit 1997047
Showing 1 changed file with 112 additions and 0 deletions.
112 changes: 112 additions & 0 deletions website/blog/2023-10-24-how-relay-enables-optimal-data-fetching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
title: How Relay Enables Optimal Data Fetching
author: Jordan Eldredge
tags: []
description:
Exploring the tradoeffs that most data fetching strategies are forced to make,
and how Relay allows you to have your cake and eat it too.
hide_table_of_contents: false
---

Relay’s approach to application authorship enables a unique combination of
optimal runtime performance and application maintainability. In this post I’ll
describe the tradeoffs most apps are forced to make with their data fetching and
then describe how Relay’s approach allows you to sidestep these tradeoffs and
achieve an optimal outcome across multiple tradeoff dimensions.

---

In component-based UI systems such as React, one important decision to make is
where in your UI tree you fetch data. While data fetching can be done at any
point in the UI tree, in order to understand the tradeoffs at play, let’s
consider the two extremes:

- Leaf node: Fetch data directly within each component that uses data
- Root node: Fetch all data at the root of your UI and thread it down to leaf
nodes using prop drilling

Where in the UI tree you fetch data impacts multiple dimensions of the
performance and maintainability of your application. Unfortunately, with naive
data fetching, neither extreme is optimal for all dimensions. Let’s look at
these dimensions and consider which improve as you move data fetching closer to
the leaves, vs. which improve as you move data fetching closer to the root.

### Loading experience

- 🚫 Leaf node: If individual nodes fetch data, you will end up with request
cascades where your UI needs to make multiple request roundtrips in series
(waterfalls) since each layer of the UI is blocked on its parent layer
rendering. Additionally, if multiple components happen to use the same data,
you will end up fetching the same data multiple times
- ✅ Root node: If all your data is fetched at the root, you will make single
request and render the whole UI without any duplicate data or cascading
requests

### Suspense cascades

- 🚫 Leaf node: If each individual component needs to fetch data separately,
each component will suspend on initial render. With the current implementation
of React, unsuspending results in rerendering from the nearest parent suspense
boundary. This means you will have to reevaluate product component code O(n)
times during initial load, where n is the depth of the tree.
- ✅ Root node: If all your data is fetched at the root, you will suspend a
single time and evaluate product component code only once.

### Composability

- ✅ Leaf node: Using an existing component in a new place is as easy as
rendering it. Removing a component is as simple as not-rendering it. Similarly
adding/removing data dependencies can be done fully locally.
- 🚫 Root node: Adding an existing component as a child of another component
requires updating every query that includes that component to fetch the new
data and then threading the new data through all intermediate layers.
Similarly, removing a component requires tracing those data dependencies back
to each root component and determining if the component you removed was that
data’s last remaining consumer. The same dynamics apply to adding/removing new
data to an existing component.

### Granular updates

- ✅ Leaf node: When data changes, each component reading that data can
individually rerender, avoiding the need to rerender unaffected components.
- 🚫 Root node: Since all data originates at the root, when any data updates it
always forces the root component to update forcing an expensive rerender of
the entire component tree.

## Relay

Relay leverages GraphQL fragments and a compiler build step to offer a more
optimal alternative. In an app that uses Relay, each component defines a GraphQL
fragment which declares the data that it needs. This includes both the concrete
values the component will render as well as the fragments (referenced by name)
of each direct child component it will render.

At build time, the Relay compiler collects these fragments and builds a single
query for each root node in your application. Let’s look at how this approach
plays out for each of the dimensions described above:

- ✅ Loading experience - The compiler generated query fetches all data needed
for the surface in a single roundtrip
- ✅ Suspense cascades - Since all data is fetched in a single request, we only
suspend once, and it’s right at the root of the tree
- ✅ Composability - Adding/removing data from a component, including the
fragment data needed to render a child component, can be done locally within a
single component. The compiler takes care of updating all impacted root
queries
- ✅ Granular updates - Because each component defines a fragment, Relay knows
exactly which data is consumed by each component. This lets relay perform
optimal updates where the minimal set of components are rerendered when data
changes

## Summary

As you can see, Relay’s use of a declarative composable data fetching language
(GraphQL), combined a compiler step, allows us to achieve optimal outcomes
across all of the tradeoff dimensions outlined above:

| | Leaf node | Root node | GraphQL/Relay |
| ------------------ | --------- | --------- | ------------- |
| Loading experience | 🚫 |||
| Suspense cascades | 🚫 |||
| Composability || 🚫 ||
| Granular updates || 🚫 ||

0 comments on commit 1997047

Please sign in to comment.