UI Performance has the potential to be the primary factor in determining UX quality, and thus growing or hurting your business. For many web applications, it can be where costs gather as the application evolves and is enhanced in other ways. Most UI libraries and frameworks that have been launched over the past number of years have reflected serious efforts by their developers to address common UI performance challenges, with varying degrees of success. React has succeeded in providing extremely effective solutions to these technical — and indeed business — problems.
Included within the release of React version 16.6.0 was such an attempt at improving performance in a very innovate way: Suspense. Announced by Dan Abramov at JSConf Iceland 2018, Suspense was described by him in the following manner:
“You can pause any state update until the data is ready, and you can add async loading to any component deep in the tree without plumbing all the props and state through your app and hoisting the logic.”
As an elucidation of this, Suspense is a React component (described in the official documentation as a “mechanism”) which may be used to suspend rendering of its child(ren) until particular data is received from an API call. It takes a prop called fallback (the value of which acts as a “catch” to the Suspense’s “try”) and renders this until the data is returned from the network request. In and of itself, Suspense is not a data-fetching library. Instead, it may work with such a library to manage rendering to the DOM in an efficient manner. Facebook integrates its Relay APIs with Suspense in its own implementation of the feature.
The differences between Suspense and other ways of rendering data via React may appear subtle at first. However, these differences are in fact important and crucial to UI performance. A common means to rendering at present is through the useEffect hook in a functional component or componentDidMount in a class component. This is a fetch-on-render approach. In each of these cases, data is fetched after the component has been rendered. This results in a sequential pattern of rendering that is known as a “waterfall” and may result in a noticeably longer wait for the user.
An alternative approach to the above is to fetch all data for various components before rendering any of them. This is a fetch-then-render approach. This prevents an overhead from occurring after the components are rendered. However, it means that none of the components may be rendered until the data for all of them have been fetched successfully. This may or may not be seen as a better technique than the fetch-on-render approach that is described above. Nevertheless, it really only succeeds in moving the delay for the user to another stage in the rendering process — a form of being ‘swept under the carpet’.
With React’s Suspense, rendering takes place only when the API network request is made. Each instance of a Suspense component is handled independently of other instances. In other words, not all data for all components needs to be ready before any component is rendered. An attempt to render the component will be made as soon as the request is made and thereafter, each time that more data is streamed from the network, another attempt will be made to render. The fallback will be rendered until the data has been successfully returned in the response.
Taking advantage of this, the structuring of the rendering may be implemented in such a way as to encompass all components in a single outer Suspense component, and then to strategically encompass certain individual components inside their own inner Suspense components. This approach is neatly described in the React documentation as a render-as-we-fetch approach as opposed to a render-after-fetch approach. As data is streamed, the parts of it that are returned earlier may be rendered before those that are returned later, and in this way, the “waterfall” consequence that was described earlier is successfully avoided.
In addition to the performance benefits that flow from Suspense, this feature also enables neater code. It removes the necessity for intrusive if (…) statements or logical && operators inside of the rendering method, thus making it easier for later refactoring or changes.
Again, as the official React documentation emphasises, Suspense is not a data-fetching library and instead is a mechanism that can be integrated with a data-fetching library. Consequently, the ultimate benefit that it enables on an application is directly impacted by the quality of the data-fetching library which is integrated with it.
Suspense creates the potential for implementing enormously efficient UIs and when used as part of concepts such as Concurrent UI Patterns, its power to take UX to the next level becomes much more clear. Applications and businesses alike should regard it as something to be embraced.