Joe Savona explored at React Conf some of the ways Relay and Suspense can help improve the user loading experience and the best practices that have been identified in production for using Suspense for data-fetching.
Savina started by recalling the problem that Suspense aims at solving. Applications often need to fetch a lot of data to show to users. In a React context, the application is implemented with components, and each component may fetch its required data when it mounts. Meanwhile, a loading indicator may be displayed. This simple pattern, dubbed in the presentation as fetch on render results in several roundtrips to the server and a waterfall of loading indicators. Relay was introduced five years ago to alleviate some of these problems.
React developers colocate in the component code the description of the data that they need:
function Post(props) {
const data = useFragment(graphql`
fragment Post_fragment on Post {
title
body
}
` , props.post);
return (
<Title>{data.title}</Title>
<Body body={data.body}> />
);
}
Relay aggregates the data dependencies across the whole application’s component tree, and may fetch it in a single roundtrip to the server. This fetch on render pattern avoids the previously shown spinner waterfall.
However, waiting for the whole data to arrive to display the whole page may still result in sub-optimal loading experience. Incrementally rendering components when they are ready may provide better user experience, in particular assuming loading indicators are replaced with placeholders that occupy the full space assigned in the layout to the component. Taking the new facebook.com as an example, Savona explained that the facebook.com’s header would display first, followed by the left navigation, followed by the stories, following by the posts and feeds. If it would occur that the news feeds is ready to display first (having received its data), it would be better to wait for and display the left navigation first, and then the newsfeeds. Otherwise, the user may suffer from brief and unpleasing layout changes.
The loading of new pages (i.e. transitions) in single-page applications may also be sped up by downloading the required data and code for the next page in parallel and having a carefully prepared transition between pages that minimize the perceived waiting time. The pattern used on facebook.com is dubbed render as you fetch.
In this context, Savona went on to describe how the pattern is implemented on Facebook’s new website. As mentioned before, Components declaratively describe their data requirement by means of a graphQL query colocated in the component code. Components that need the fetching of remote resources to render are wrapped in the <Suspense fallback={<PlaceHolder/>}>/>
component. React then displays a fallback while fetching the necessary resource. In order to coordinate the rendering of pending components, the <SuspenseList/>
component is used:
function Home(props) {
return (
<ErrorBoundary fallback={<ErrorMessage/>}>
<SuspenseList revealOrder="forwards">
<Suspense fallback={<ComposerFallback/>}>
<Composer />
</Suspense>
<Suspense fallback={<FeedFallback/>}>
<NewsFeed/>
</Suspense>
</SuspenseList>
</ErrorBoundary>
)
}
With the previous code, the newsfeed and composer will render their placeholder first. The composer will render before the newsfeed independently of the order in which the fetched resources arrive:
Additionally, the <Suspense />
component will optimize further the perceived loading performance by waiting a short amount of time when the first component is ready to render. If within that time the second component is ready to render, then both components are rendered together. React’s user research has shown the positive impact of that pattern:
it turns out that users’ perception of performance is determined by more than the absolute loading time. For example, when comparing two apps with the same absolute startup time, our research shows that users will generally perceive the one with fewer intermediate loading states and fewer layout changes as having loaded faster.
Developers should also note the <ErrorBoundary/>
component which handles the case of erroneous resource fetching.
Additionally, in handlers of events triggering page transitions, Facebook developers use a specific API by which the resources (code and data) for the next page are identified. The fetching of such resources thus starts in the handler, in parallel, before any rendering of the next page.
Savona’s full talk is available on ReactConf’s site and contains further code snippets, animated demos, and detailed explanations. React Conf is the official Facebook React event. React Conf was held in 2019 in Henderson, Nevada, on October 24 & 25.