If you follow React, you’ve undoubtedly heard of React Suspense, a component that allows you to gracefully handle loading and rendering data in your React projects. At the time of writing, React Suspense is still in the experimental stage.
To further develop the ideas behind React Suspense, the React Freeze project essentially enables you to freeze component rendering and control what is actually updated in your React apps. This approach works well with React Native projects, as well as regular React web applications.
In this article, we’ll walk through React Freeze, learning how to use it in our apps. First, I’ll introduce React Suspense, then show how it works in a demo project. Next, I’ll show how React Freeze can enhance what you see with React Suspense.
If you’d like to follow along, I have sample React Suspense and React Freeze implementations at my react-suspense-and-freeze GitHub repo. Let’s get started!
React Suspense is an experimental concept that is available in React 18. To install React Suspense, I recommend installing React 18 and reviewing the information in this GitHub thread. Essentially, React Suspense allows you to gracefully handle loading data by suspending rendering until all the parts of your components are ready to display.
A common problem developers face in frontend development is that you may need to wait for an API call, or you may want to control what is shown to the user so they don’t see incomplete data. React Suspense provides a suspense
component, which includes a fallback
that is shown while the component loads.
Check out the example below, which was originally copied from the CodePen project:
function ProfilePage() { return ( <Suspense fallback={<h1>Loading profile...</h1>} > <ProfileDetails /> <Suspense fallback={<h1>Loading posts...</h1>} > <ProfileTimeline /> </Suspense> </Suspense> ); }
As you can see in the code above, there is a <ProfileDetails />
component as well as <ProfileTimeline />
component.
The fallback
is a basic <h1>
element that just has the words Loading profile…
and Loading posts…
. With this functionality, you don’t have to add any conditional statements or use useEffect
in your code to verify if something is loaded.
The example below, originally copied from the React QuickStart example, includes a suspender
implementation that mimics an API call. There are a lot of mechanisms that we use to handle such activity, like try...catch
blocks, as well as libraries like Axios. You can see it in the wrapPromise
function in the fakeApi.js
file below:
function wrapPromise(promise) { let status = "pending"; let result; let suspender = promise.then( (r) => { status = "success"; result = r; }, (e) => { status = "error"; result = e; } ); return { read() { if (status === "pending") { throw suspender; } else if (status === "error") { throw result; } else if (status === "success") { return result; } } }; }
Wrapping the <Suspense>
element handles the result of this call with the fallback
elements.
These concepts could be super useful to developers, and I’m excited to see them in a future release of React. If you’d like to learn more about where this feature is in development, check out the React Docs.
React Freeze builds on the ideas presented in React Suspense, enabling you to pause component rendering for a good user experience. The approach is similar to React Suspense as you can see in the following example, which was copied from the React Freeze GitHub repo:
function SomeComponent({ shouldSuspendRendering }) { return ( <Freeze freeze={shouldSuspendRendering}> <MyOtherComponent /> </Freeze> ); }
In the example above, you wrap your component with a <Freeze>
element. You then pass a boolean
flag to the Freeze
element to determine if the child component is rendered or not. Doing so is advantageous within web applications because you can control your application’s rendering, potentially even preventing incomplete data from rendering unnecessarily.
If you’re following along in my sample project, look in the react-freeze-sample-project
folder and you’ll see the following code:
profileResponse === null ? <h1>Loading profile...</h1> : <> <button onClick={() => callService()}>refresh</button> <Freeze freeze={profileResponse === null}> <ProfileDetails user={profileResponse} /> { postsResponse === null ? <h1>Loading posts...</h1> : <Freeze freeze={postsResponse === null}> <ProfileTimeline posts={postsResponse} /> </Freeze> } </Freeze> </> );
Similar to what we did with Suspense, we wrap our components with a <Freeze>
element, then determine when to show them. My sample project is very simple, but you can imagine how useful this could be in a larger application.
Like having a Suspender
mechanism, if you look at the React Freeze source code, you see how the render is actually handled:
function Suspender({ freeze, children, }: { freeze: boolean; children: React.ReactNode; }) { const promiseCache = useRef<StorageRef>({}).current; if (freeze && !promiseCache.promise) { promiseCache.promise = new Promise((resolve) => { promiseCache.resolve = resolve; }); throw promiseCache.promise; } else if (freeze) { throw promiseCache.promise; } else if (promiseCache.promise) { promiseCache.resolve!(); promiseCache.promise = undefined; } return <Fragment>{children}</Fragment>; }
You can also enable this behavior in React Native applications by importing the Freeze
element:
import { enableFreeze } from "react-native-screens"; enableFreeze(true);
React Native applications handle screens with a navigation stack, meaning that as a user progresses, the previous screen’s state is held on a stack for future use. If you implement React Freeze with React Native, you can control what is rendered on the different screens.
My sample project just has Suspense and Freeze implementations in a web application; if you’d like to see an example of React Freeze with React Native, check out the sample project by Natanaelvich at react-freeze-example.
When I was writing this post, I found Chrome DevTools very helpful with visualizing the rendering. If you open Chrome DevTools and select rendering, you can select paint flashing, which will paint sections of your page to be rendered like in the following image:
I also recommend installing the React developer tools extension on Chrome. If you do this, you can open it in Chrome DevTools and view what is rendered:
In this post, we covered both React Freeze and React Suspense. React Suspense is a powerful concept that I hope will be available in future React releases. React Freeze provides a solid implementation of similar concepts that can be used in both web and React Native applications.
There is still some work to be done to standardize this behavior, but the strategy of controlling what is rendered provides both a solid user experience and performant implementation for React projects. I also recommend checking out my sample project, and playing with Chrome DevTools to see this in action.
Thanks for reading my post! Follow me on andrewevans.dev and connect with me on Twitter at @AndrewEvans0102.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]