Andrew Evans Husband, engineer, FOSS contributor, and manager at CapTech. Follow me at rhythmandbinary.com and andrewevans.dev.

Exploring React Suspense with React Freeze

4 min read 1178

React Suspense React Freeze

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!

What is React Suspense?

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.

What is React Freeze?

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>;
}

React Native and React Freeze

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.

Visualizing Freeze and Suspense

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:

Chrome DevTools Paint Flashing

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:

React Dev Tools Extension

Wrapping Up

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.


More great articles from LogRocket:


Thanks for reading my post! Follow me on andrewevans.dev and connect with me on Twitter at @AndrewEvans0102.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Andrew Evans Husband, engineer, FOSS contributor, and manager at CapTech. Follow me at rhythmandbinary.com and andrewevans.dev.

Leave a Reply