DEV Community

vaukalak
vaukalak

Posted on

3

React 18 Suspense minimal example

In the current version of React (17.0.2 at the day of this article creation) Suspense is a component, that allows developers to lazy-load application parts. It accepts fallback property, with content to display, while the child component is lazy-loading.

const SomePage = React.lazy(() => import("./modules/somepage"));

const App () => (
  <React.Suspense fallback={"Loading..."}>
    <SomePage />
  </React.Suspense>
);
Enter fullscreen mode Exit fullscreen mode

However from React 18 it will be possible to use Suspense for data fetching. This means, that fallback will be displayed until component will fetch all the data needed. Or in general all events that component expect will occur. Let say we want to just display placeholder for 2 seconds:

const Main = () => {
  useTimeout(2000);
  return <div>Component loaded</div>;
};

const App = () => (
  <Suspense fallback={"Loading..."}>
    <Main />
  </Suspense>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

As you could guess, Loading... will be displayed for 2 seconds and Component loaded afterwards.
However when I first saw the code above, I didn't understand HOW did that happen.. What is that magic mechanism in the useTimeout hook? In short it has to:

  1. Stop component code execution.
  2. Let Suspense know that the component isn't yet ready
  3. Notify Suspence know when it should re-attempt with rendering component.

To stop code execution you need use throw statement. In order to make Suspense know it's expected, the value thrown need to be a Promise. Suspense will catch this promise and subscribe to it, to re-attempt rendering.
Please note: the code bellow is just for a demo purpose:

let fullfilled = false;
let promise = null;

const useTimeout = (ms: number) => {
  // check if timeout already occurred.
  if (!fullfilled) {
    // if promise doesn't exist create and throw it.
    throw promise ||= new Promise((res) => {
      setTimeout(() => {
        // on next attempt consider timeout completed.
        fullfilled = true;
        // resolve promise (will ask react to re-render).
        res();
      }, ms);
    });
  }
};
Enter fullscreen mode Exit fullscreen mode

(Confused about ||= ? Check this doc)

It turns out that suspense uses quite simple mechanisms, but there's a hard part. You might ask why fullfilled and promise couldn't be stored in a ref, so the hook would be reusable:

const fullfilled = useRef(false);
Enter fullscreen mode Exit fullscreen mode

It turns out, that while component is not loaded, hooks can't be really used. Component will be unmounted / mounted on every render attempt before the render will complete without throwing promises. Hence to figure out, if this component has actually started data loading process, we should rely on a globally available cache. (In our simplified case it's just fullfilled variable). Of course in a real-world example such simple approach wouldn't work (this hook works only one time).

This is why it's advised to use good libraries that supports suspense (like swr).

Full code of the example above.

👋

SurveyJS custom survey software

Build Your Own Forms without Manual Coding

SurveyJS UI libraries let you build a JSON-based form management system that integrates with any backend, giving you full control over your data with no user limits. Includes support for custom question types, skip logic, an integrated CSS editor, PDF export, real-time analytics, and more.

Learn more

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay