Source: React Blog

React Blog Building Great User Experiences with Concurrent Mode and Suspense

At React Conf 2019 we announced an experimental release of React that supports Concurrent Mode and Suspense. In this post we'll introduce best practices for using them that we've identified through the process of building the new facebook.com.This post will be most relevant to people working on data fetching libraries for React.It shows how to best integrate them with Concurrent Mode and Suspense. The patterns introduced here are based on Relay - our library for building data-driven UIs with GraphQL. However, the ideas in this post apply to other GraphQL clients as well as libraries using REST or other approaches.This post is aimed at library authors. If you're primarily an application developer, you might still find some interesting ideas here, but don't feel like you have to read it in its entirety.Talk VideosIf you prefer to watch videos, some of the ideas from this blog post have been referenced in several React Conf 2019 presentations:Data Fetching with Suspense in Relay by Joe SavonaBuilding the New Facebook with React and Relay by Ashley WatkinsReact Conf Keynote by Yuzhi ZhengThis post presents a deeper dive on implementing a data fetching library with Suspense.Putting User Experience FirstThe React team and community has long placed a deserved emphasis on developer experience: ensuring that React has good error messages, focusing on components as a way to reason locally about app behavior, crafting APIs that are predictable and encourage correct usage by design, etc. But we haven't provided enough guidance on the best ways to achieve a great user experience in large apps.For example, the React team has focused on framework performance and providing tools for developers to debug and tune application performance (e.g. React.memo). But we haven't been as opinionated about the high-level patterns that make the difference between fast, fluid apps and slow, janky ones. We always want to ensure that React remains approachable to new users and supports a variety of use-cases - not every app has to be "blazing" fast. But as a community we can and should aim high. We should make it as easy as possible to build apps that start fast and stay fast, even as they grow in complexity, for users on varying devices and networks around the world.Concurrent Mode and Suspense are experimental features that can help developers achieve this goal. We first introduced them at JSConf Iceland in 2018, intentionally sharing details very early to give the community time to digest the new concepts and to set the stage for subsequent changes. Since then we've completed related work, such as the new Context API and the introduction of Hooks, which are designed in part to help developers naturally write code that is more compatible with Concurrent Mode. But we didn't want to implement these features and release them without validating that they work. So over the past year, the React, Relay, web infrastructure, and product teams at Facebook have all collaborated closely to build a new version of facebook.com that deeply integrates Concurrent Mode and Suspense to create an experience with a more fluid and app-like feel.Thanks to this project, we're more confident than ever that Concurrent Mode and Suspense can make it easier to deliver great, fast user experiences. But doing so requires rethinking how we approach loading code and data for our apps. Effectively all of the data-fetching on the new facebook.com is powered by Relay Hooks - new Hooks-based Relay APIs that integrate with Concurrent Mode and Suspense out of the box.Relay Hooks - and GraphQL - won't be for everyone, and that's ok! Through our work on these APIs we've identified a set of more general patterns for using Suspense. Even if Relay isn't the right fit for you, we think the key patterns we've introduced with Relay Hooks can be adapted to other frameworks.Best Practices for SuspenseIt's tempting to focus only on the total startup time for an app - but 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. Suspense is a powerful tool for carefully orchestrating an elegant loading sequence with a few, well-defined states that progressively reveal content. But improving perceived performance only goes so far - our apps still shouldn't take forever to fetch all of their code, data, images, and other assets.The traditional approach to loading data in React apps involves what we refer to as "fetch-on-render". First we render a component with a spinner, then fetch data on mount (componentDidMount or useEffect), and finally update to render the resulting data. It's certainly possible to use this pattern with Suspense: instead of initially rendering a placeholder itself, a component can "suspend" - indicate to React that it isn't ready yet. This will tell React to find the nearest ancestor <Suspense fallback={<Placeholder/>}>, and render its fallback instead. If you watched earlier Suspense demos this example may feel familiar - it's how we originally imagined using Suspense for data-fetching.It turns out that this approach has some limitations. Consider a page that shows a social media post by a user, along with comments on that post. That might be structured as a <Post> component that renders both the post body and a <CommentList> to show the comments. Using the fetch-on-render approach described above to implement this could cause sequential round trips (sometimes referred to as a "waterfall"). First the data for the <Post> component would be fetched and then the data for <CommentList> would be fetched, increasing the time it takes to show the full page.There's also another often-overlooked downside to this approach. If <Post> eagerly requires (or imports) the <CommentList> component, our app will have to wait to show the post body while the code for the comments is downloading. We could lazily load <CommentList>, but then that would delay fetching comments data and increase the time to show the full page. How do we resolve this problem without compromising on the user experience?Render As You FetchThe fetch-on-render approach is widely used by React apps today and can certainly be used to create great apps. But can we do even better? Let's step back and consider our goal.In the above <Post> example, we'd ideally show the more important content - the post body - as early as possible, without negatively impacting the time to show the full page (including comments). Let's consider the key constraints on any solution and look at how we can achieve them:Showing the more important content (the post body) as early as possible means that we need to load the code and data for the view incrementally. We don't want to block showing the post body on the code for <CommentList> being downloaded, for example.At the same time we don't want to increase the time to show the full page including comments. So we need to start loading the code and data for the comments as soon as possible, ideally in parallel with loading the post body.This might sound difficult to achieve - but these constraints are actually incredibly helpful. They rule out a large number of approaches and spell out a solution for us. This brings us to the key patterns we've implemented in Relay Hooks, and that can be adapted to other data-fetching libraries. We'll look at each one in turn and then see how they add up to achieve our goal of fast, delightful loading experiences:Parallel data and view treesFetch in event handlersLoad data incrementallyTreat code like dataParallel Data and View TreesOne of the most appealing things about the fetch-on-render pattern is that it colocates what data a component needs with how to render that data. This colocation is great - an example of how it makes sense to group code by concerns and not by technologies. All the issues we saw above were due to when we fetch data in this approach: upon rendering. We need to be able to fetch data before we've rendered the component. The only way to achieve that is by extracting the data dependencies into parallel data and view trees.Here's how that works in Relay Hooks. Continuing our example of a social media post with body and comments, here's how we might define it with Relay Hooks:// Post.js function Post(props) { // Given a reference to some post - `props.post` - *what* data // do we need about that post? const postData = useFragment(graphql` fragment PostData on Post @refetchable(queryName: "PostQuery") { author title # ... more fields ... } `, props.post); // Now that we have the data, how do we render it? return ( <div> <h1>{postData.title}</h1> <h2>by {postData.author}</h2> {/* more fields */} </div> ); }Although the GraphQL is written within the component, Relay has a build step (Relay Compiler) that extracts these data-dependencies into separate files and aggregates the GraphQL for each view into a single query. So we get the benefit of colocating concerns, while at runtime having parallel data and view trees. Other frameworks could achieve a similar effect by allowing developers to define data-fetching logic in a sibling file (maybe Post.data.js), or perhaps integrate with a bundler to allow defining data dependencies with UI code and automatically extracting it, similar to Relay Compiler.The key is that regardless of the technology we're using to load our data - GraphQL, REST, etc - we can separate what data to load from how and when to actually load it. But once we do that, how and when do we fetch our data?Fetch in Event HandlersImagine that we're about to navigate from a list of a user's posts to the page for a specific post. We'll need to download the code for that page - Post.js - and also fetch its

Read full article »
Est. Annual Revenue
$100K-5.0M
Est. Employees
1-25
CEO Avatar

Founder

Jordan Walke

CEO Approval Rating

- -/100



React's headquarters is located in Menlo Park, California. Jordan Walke is the Founder of React. React has 3 followers on Owler.