DEV Community

Cover image for How to use... "use", the new React 19 hook
Aidan
Aidan

Posted on

How to use... "use", the new React 19 hook

TLDR

Use let's you read the value of a Promise or a Context inside a component

const value = use(resource)

A short example we can use it to read context or promise values like so:

import { use } from "react";

function MessageComponent ({ messagePromise }) {
    const message = use(messagePromise);
    const theme = use(ThemeContext);
}
Enter fullscreen mode Exit fullscreen mode

If you want to watch rather than read, or want a code-walk through of using use with Context and Promises check this video out

The longer explanation...

The component where use is called needs to be integrated with Suspense and optionally error boundaries. While resolving the promise and getting the data from context, the fallback of Suspense will be shown. Once the Promise is resolved, the fallback is replaced by the rendered components using the data returned by the use API.

If the Promise passed to use is rejected, the fallback of the nearest Error Boundary will be displayed.

Using "use" with React Context

Setting up Context

First, we need to get an app hooked up with Context.

Say we want to set up a custom theme, light and dark. We should create a file called ThemeContext.ts:

// ThemeContext.ts
import { createContext, Dispatch, SetStateAction } from 'react';

type Theme = 'light' | 'dark';

interface IThemeContext {
  theme: Theme;
  setTheme: Dispatch<SetStateAction<Theme>>;
}

export const ThemeContext = createContext<IThemeContext>({
  theme: 'dark',
  setTheme: () => {},
});
Enter fullscreen mode Exit fullscreen mode

The above creates our ThemeContext object, which we can import from any file to grab the current theme and the setTheme will be used to update said theme. We're defaulting the theme to be "dark" as that looks a lot cooler.

Then to store all of our apps contexts we will create a Context.tsx component, to keep our app nicely organised:

// Context.tsx
import React, { useState } from 'react';
import { ThemeContext } from './ThemeContext';
import { Theme } from '../types';

export default function Context({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>('dark');

  return (
    <ThemeContext.Provider
      value={{
        theme,
        setTheme: setTheme,
      }}
    >
      {children}
    </ThemeContext.Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

We will pass the state object to our ThemeContext.Provider, which will be used for reading and updating theme through our app. Finally, to get this all hooked up, we need to wrap our <App /> component in our <Context /> component.

// index.tsx

// ... Other imports etc.
import Context from './Context.tsx'

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <Context>
      <App />
    </Context>
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

Now <App /> and its children (which is basically our whole project) will have access to our ThemeContext.

Putting "use" to use with Context

Let's create a component that will update the theme of our app! Components/ContextExample.tsx:

// ContextExample.tsx
import { use } from 'react';
import { ThemeContext } from '../context/ThemeContext';

export default function ContextExample() {
  const { setTheme } = use(ThemeContext);

  const handleThemeToggle = () => {
    setTheme((prevTheme) => {
      if (prevTheme === 'light') {
        return 'dark';
      } else {
        return 'light';
      }
    });
  };

  return (
    <div>
      <button onClick={() => handleThemeToggle()}>Switch Theme</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

We're grabbing the setTheme method from ThemeContext. We're rendering a <button> that onClick calls handleThemeToggle() which will check the current theme. If it's currenly it "light" we set the Theme to be "dark" and visa versa.

However... this won't do anything until we import it and set-up some styles. In our App.tsx:

// App.tsx
// Other imports...
import { ThemeContext } from './context/ThemeContext';
import ContextExample from './components/ContextExample';

function App() {
  const { theme } = use(ThemeContext);

  return (
    <div className={`App ${theme}`}>
      <ContextExample />
    </div>
  )
}

export default App;

Enter fullscreen mode Exit fullscreen mode

We're importing and passing the theme from ThemeCotnext as a class to our App.tsx's wrapping <div >. Now we can go into our App.css file and add some styles to see if our Theme switching works!:

/* App.css */
.dark {
  background-color: black;
  color: white;
}

.light {
  background-color: white;
  color: black;
}
Enter fullscreen mode Exit fullscreen mode

It ain't much... but it'll show us if our use of use and React Context is up to scratch. Go on! Give that button a toggle.

Dark Theme

Light Theme

Will you look at that?! Ugly, but functional. That concludes using "use" with React Context. Carry on reading if you want to see if in action with a Promise!

Using "use" with an Async Promise

First of all, we will create a server action, for more information about server actions and how to use them with AWS check out this video.

Create a new file called lib.ts:

// lib.ts
'use server';

export const fetchMessage = async () => {
  await new Promise((resolve) => setTimeout(resolve, 2500));
  return 'Secret Message Recieved!';
};
Enter fullscreen mode Exit fullscreen mode

We're "faking" an API request here, in a real app this would be a call to a database or a fetch request to some API to grab some external data. But by using an await, Promise() and setTimeout we can make our Promise last 2.5 seconds in this example.

We need to create a couple of components to get this working, a wrapper that'll use Suspense then inside that component and another component that'll use use to grab the result of our fetchMessage() Promise we just created in lib.ts.

Create these two new files Components/MessageContainer.tsx & Components/PromiseExample.tsx:

MessageContaienr.tsx:

// MessageContainer.tsx
import { Suspense } from 'react';
import PromiseExample from './PromiseExample';

export default function MessageContainer({
  messagePromise,
}: {
  messagePromise: Promise<string>;
}) {
  return (
    <Suspense fallback={<p>Waiting for the message...</p>}>
      <PromiseExample messagePromise={messagePromise} />
    </Suspense>
  );
}
Enter fullscreen mode Exit fullscreen mode

The <p> inside the fallback is what will display while we wait for our Promise to resolve.

Now for the PromiseExample.tsx file:

// PromiseExample
'use client';

import { use } from 'react';

export default function PromiseExample({
  messagePromise,
}: {
  messagePromise: Promise<string>;
}) {
  const messageConent = use(messagePromise);
  return <p>Here's the message: {messageConent}</p>;
}
Enter fullscreen mode Exit fullscreen mode

Here's where the magic happens, we're using the use component to get the resolved promise value, the string "Secret Message Recieved" we return in our fetchMessage() Promise. How easy is that? use() and <Suspense> sure make working with Asynchronous data a breeze...

Let's hook up our MessageContainer to the App.tsx so we can see it in action!
App.tsx:

// App.tsx

// Other imports...
import { fetchMessage } from './lib';
import MessageContainer from './components/MessageContainer';

function App() {
  const { theme } = use(ThemeContext);

  const [messagePromise, setMessagePromise] = useState<Promise<string> | null>(
    null
  );

  const handleFetchMessage = () => {
    setMessagePromise(fetchMessage());
  };

  return (
        <div className={`App ${theme}`}>
        <ContextExample />
        <button onClick={() => handleFetchMessage()}>Send Message</button>
        {messagePromise && <MessageContainer messagePromise={messagePromise} />}
    </div>
  )
}

export default App;
Enter fullscreen mode Exit fullscreen mode

We create a new state object to store the messagePromise, defaulting it to null. This is so we can hide the message if we haven't requested it yet.
The handleFetchMessage() function simply sets the messagePromise state to contain the message Promise from our fetchMessage server action. We created a <button> to call handleFetchMessage. Finally, we conditionally show <MessageCotnainer /> on our FE depending on the state of messagePromise, this means it will only show our <MessagePromiseContainer /> component once the fetchMessage() promise has been initiated.

So after hitting this button: <button onClick={() => handleFetchMessage()}>Send Message</button>. We call our fetchMessage() server action, the fallback in the Suspense is triggered while use() waits for the Promise to be resolved.

Promise is being resolved, showing the suspenses fallback

Once the Promise has been resolved, we use the data we extracted from the Promise using the use hook and display that to our FE:

Resolved message from the promise

Phew... that was a lot, I hope it makes sense! The key part to remember while working with use and Promises is to wrap the Component you're using use within a Suspense and after that, it's as simple as calling use(promiseIWantTheValueOf) <-- Awful variable name right?

Conclusion

Use, unlike React Hooks, can be used inside an if or for statement. However, use can Only be used inside a Component or Hook. I can see this being handy when you have different privileges in your application. You may have one API req for Admins and another for normal users. Before you couldn't call React hooks conditonally, however use is breaking the mold here.

So you can do something like:

if (isAdmin) {
  use(getAllUsers)
} else {
  use(getUsersInMyAccount)
}
Enter fullscreen mode Exit fullscreen mode

^ Please comment below if you can think of better examples of this new superpower of being able to conditionally call the hook.

React sure is making working with servers and asynchronous code easier, with a focus towards SSR and working with forms, I'm all for it.

If you read this far I'm very impressed! I didn't think it would take this much explaining but I think it's important to see where/how this hook is going to be useful.

Thank you all and if you want to support me, please give my YouTube Channel a look as I look to go more in-depth into everything React & Web Dev in general!

Top comments (1)

Collapse
 
aidanldev profile image
Aidan