Code Your Path Coding School

Demystifying Fallback UI, Suspence, and Error Boundaries With React use Hook (API) – 5 Best Code Examples With New React Feature

React use Hook (API)

Table of Contents

React 19 introduces a new React use hook — a fresh API to read resources during render.

use brings to React components the ease and syntax familiarity of async/await declarations. If you’ve ever juggled promises and state updates in React, you know how much of a pain that is.

Whereas some call it a “hook,” the “use” hook DOESN’T impose the exact requirements around conditional code that traditional hooks (useState or useEffect) do — call “use” WITHOUT worrying about the Rules of Hooks (hopefully).

You also can call the use API with a combination of Promises and <Contexts>, which is confusing at first but simplifies our codebase in the long run.

What Is Reacty use Hook — New “use” API Vs async/await

The “use” API works the same way as promises, managing an internal fulfillment state.

use() processes data from context or any other source, handling them as “consumables”. That makes it a flexible alternative to the async/await statements when implementing asynchronous operations in your React app.

`<async>`/`<await>` Example

In JavaScript “Promises Land”, we use of the async function expression to declare a function and await keyword for consuming it.

const DataFetchingComponent = () => {
  const [data, setData] = useState(null);

  // Async / await Function for GETting Data
  const fetchData = async () => {
    const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
    setData(response.data);
  };

  // This React hook simulates the behavior of componentDidMount in class components.
  useEffect(() => {
    fetchData();
  }, []);

  return (
    <div>
      <h1>Fetched Data:</h1>
      <ul> Name: {userData.name} </ul>
    </div>
  );
};

We use useState to manage the data state and async/await to handle asynchronous operations.

fetchData is an async function that “awaits” the promise of axios.get(), which polls data from a JSONPlaceholder API.

React use Hook (API) Example

The use API provides a similar model, allowing you to handle asynchronous resources without the need for the async/await operators.

You can pass a `<Promise>` as a prop from a Server Component to a Client Component.

function fetchUser() {
  return axios
    .get("https://jsonplaceholder.typicode.com/users/1")
    .then((response) => response.data);
}

export default function App() {
  return (
    <>
      <UseData dataPromise={fetchUser()} />
    </>
  );
}

Again, the fetchUser function uses axios to fetch data and returns a Promise that resolves with the user data (name, email, address, phone). But this time, this is a basic synchronous function without the <async function> expression.

When calling the fetchUser function, axios returns a promise, and we pass it to the <UseData> component.

"use client";

import { use, Suspense } from "react";
import User from "./User";

function Message({ messagePromise }) {
  const messageContent = use(messagePromise);
  return <User userData={messageContent} />;
}

export default function UseData({ messagePromise }) {
  return (
    <Suspense fallback={<p>⌛Downloading message...</p>}>
      <h2>Use Data:</h2>
      <Message messagePromise={messagePromise} />
    </Suspense>
  );
}

Inside the UseData component, I wrap the Message component in Suspense to display the fallback until JS resolves the Promise.

The Message component calls the use() function to read the messagePromise from the fetchUser function. It suspends the rendering of this component while data is loading. Once the data is there, messageContent returns data, and Message displays it!! Here’s the full CodeSandbox — feel free to fork!

Combining use and Suspense simplifies data fetching, making the component logic cleaner (and more intuitive).

The use() function is still experimental but has already made its way into Next.js, React-Query library, and SWR package — all three use Canary React team version.

In particular, the use API does NOT have the usual “unstable_” prefix (meaning it’s stable for production development).

React use Hook (API) Use Case #1 – Consume Context

You can pass a context to the use function similarly to the useContext hook. Simply call use() and access data wherever you want to (in conditions, if-else, and loops)

Consider a use case where you toggle between dark and light modes in your application using the use API:

function App() {
  const [isDarkMode, setIsDarkMode] = useState(false);

  return (
    <ThemeContext.Provider value={isDarkMode ? "dark" : "light"}>
      <Dashboard setIsDarkMode={setIsDarkMode} />
    </ThemeContext.Provider>
  );
}

I use the useState React hook to initialize an isDarkMode flag to store the current theme state.

I then wrap our Dashboard component inside a <ThemeContext.Provider> and use the current theme mode, either “dark” or “light” strings, for its value.

This also gives any nested component access to the theme value with use or useContext. (I prefer use() API over useContext hook because it is more flexible)

The Dashboard component can now access setIsDarkMode to toggle between dark and light modes.

function Dashboard({ setIsDarkMode }) {
  const theme = use(ThemeContext);
  const className = "dashboard-" + theme;

  return (
    <section className={className}>
      <h1>Welcome</h1>
      <Button onClick={() => setIsDarkMode((prev) => !prev)}>Switch</Button>
    </section>
  );
}

This approach keeps your frontend code neat, clean, and understandable! (Check the full Codesand)

React use API Use Case #2 — Process Promise and Suspense Child

An everyday use case is to pass a Promise to use(). If you put this in a Suspense boundary, React will suspend rendering until the promise resolves with the fallback UI displayed instead.

Below is a simple example of a way in which the use() function in combination with React.Suspense to process async data. (Read more in my Real-World React Suspense Examples post)

// Simulate fetching data with a promise
const fetchMessage = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("Data Fetched!");
    }, 2000);
  });
};

function MessageComponent() {
  // Use the use function to read the promise
  const message = use(fetchMessage());

  return (
    <div>
      <p>{message}</p>
    </div>
  );
}

function App() {
  return (
    <div>
      <h1>My App</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <MessageComponent />
      </Suspense>
    </div>
  );
}

In the App component, I wrap MessageComponent with a Suspense boundary. Suspense is a React component that falls back content while waiting for async operations. It provides a fallback div with “Loading…” text to display while MessageComponent waits for its data to resolve.

We simulate a fetchMessage data-fetching function — it returns a Promise that resolves to “Hello, world!” after 2 seconds.

In the MessageComponent, we call use() function to read the Promise, and while the promise is still pending, React will suspend the rendering of this component.

While the promise is pending, React will show the user the fallback UI — in this case, <div>Loading...</div>. When the promise resolves, MessageComponent renders the message. (Check the full Codesanbox)

A mixture of use with Suspense handles asynchronous data fetching cleanly and efficiently, with minimal DX (developer experience) complexity.

React use API Hook (API) Use Case #3 – Conditions

The “use” API is very flexible — unlike traditional hooks, you can nest “use” into conditions, blocks, and loops.

This means that you can wait for data to load conditionally, not having to break your logic apart just for this, making your code cleaner and more intuitive.

For example, the ConditionalComponent component conditionally calls the use() function to fetch and render a message only if BOTH  “shouldFetch” and “shouldSucceed” states are true

const fetchData = (shouldSucceed) => new Promise((resolve, reject) => {
  setTimeout(() => shouldSucceed ? resolve("Data fetched successfully!") : reject("Failed to fetch data."), 2000);
});

function ConditionalComponent({ shouldFetch }) {
  if (!shouldFetch) {
    return <div>No data to fetch.</div>;
  }

  const promise = fetchData(true);
  const data = use(promise);

  return <div>{data}</div>;
}

function App() {
  return (
    <div>
      <h1>Conditional Fetching</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <ConditionalComponent shouldFetch={true} />
      </Suspense>
    </div>
  );
}

Here, I call the use API to suspend a Promise inside a component immediately.

The fetchData function simulates data fetching with a 2-second delay and resolves or rejects depending on the shouldSucceed argument.

The ConditionalComponent checks if it should fetch data by looking at the shouldFetch prop. If true — it calls fetchData and call use() to handle the promise, rendering the result when the Promise resolves.

#4 Learn One More Conditional React use function Use Case

Another example of a conditional use API usage would be optional chaining.

In this example, the UserForm component takes an email prop and uses it to fetch user data conditionally and then one more time if the user exists:

function UserForm({ email }) {
  const user = use(fetch(`/api/users?email=${email}`));
  const [userID, setUserID] = useState(null);
  const userHistory = userID ? use(fetch(`/api/users/history?id=${userID}`)) : null;

  // Render logic for your component goes here
}

We call the use API to fetch user data by email. Then, we conditionally fetch the user’s history ONLY if userID is not null (meaning we successfully found and fetched a user from DB).

When using hooks conditionally, you can handle edge cases, such as what happens if the data fetch fails.

React use Function Use Case #5 — First Class Support Of Error Boundaries

You can use an error boundary if you’d like to display an error to your users when JS rejects promises.

Wrap the component where you are calling the use API in an error boundary and provide a fallback UI. If the Promise fails, React will display a fallback for the error boundary.

import { ErrorBoundary } from 'react-error-boundary';

function Data({ dataPromise }) {
  const data = use(dataPromise);
  return <p>Fetched data: {data}</p>;
}

export function DataContainer({ promise }) {
  return (
    <ErrorBoundary FallbackComponent={<>Error Occurred!</>}>
      <Suspense fallback={<>...Fetching...</>}>
        <Data dataPromise={promise} />
      </Suspense>
    </ErrorBoundary>
  );
}

The DataContainer component wraps the Data component with both ErrorBoundary and Suspense.

The ErrorBoundary catches any errors that occur during the rendering of its children and displays a fallback UI (<>Error Occurred!</>) if an error is thrown.

The Suspense component will, in turn, render a loading fallback (<>…Fetching…</>) until the promise (dataPromise) resolves.

This setup gives us a very strong mechanism for error and loading state management — it combines the “use” API to handle async, Suspense with ErrorBoundary to handle loading, and ErrorBoundary for error handling.

Conclusion – 5 Examples Of React Use Hook (API) Usage Solutions

The new React the use() hook (API) is a powerful function that simplifies the management of `<Contexts>,` fallbacks and `<Suspense>,` `<Promises>,` and Error Boundaries.

As you can see from these five examples, use() offers a cleaner and more efficient way to manage your React apps. Try these patterns in your projects and share the results. (Read more about React 19 Features and React Compiler)

Share the Post:
Join Our Newsletter