Skip to content

Latest commit

 

History

History
629 lines (478 loc) · 21.5 KB

File metadata and controls

629 lines (478 loc) · 21.5 KB
title use

use is a React API that lets you read the value of a resource like a Promise or context.

const value = use(resource);

Reference {/reference/}

use(resource) {/use/}

Call use in your component to read the value of a resource like a Promise or context.

import { use } from 'react';

function MessageComponent({ messagePromise }) {
  const message = use(messagePromise);
  const theme = use(ThemeContext);
  // ...

Unlike React Hooks, use can be called within loops and conditional statements like if. Like React Hooks, the function that calls use must be a Component or Hook.

When called with a Promise, the use API integrates with Suspense and Error Boundaries. The component calling use suspends while the Promise passed to use is pending. If the component that calls use is wrapped in a Suspense boundary, the fallback will be displayed. Once the Promise is resolved, the Suspense 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.

See more examples below.

Parameters {/parameters/}

  • resource: this is the source of the data you want to read a value from. A resource can be a Promise or a context.

Returns {/returns/}

The use API returns the value that was read from the resource like the resolved value of a Promise or context.

Caveats {/caveats/}

  • The use API must be called inside a Component or a Hook.
  • When fetching data in a Server Component, prefer async and await over use. async and await pick up rendering from the point where await was invoked, whereas use re-renders the component after the data is resolved.
  • Prefer creating Promises in Server Components and passing them to Client Components over creating Promises in Client Components. Promises created in Client Components are recreated on every render. Promises passed from a Server Component to a Client Component are stable across re-renders. See this example.
  • A Promise used in Client Components and passed to use must be cached or stable between renders (e.g. not recreated between renders); otherwise each render creates a new Promise and the component may suspend indefinitely.
  • A Promise passed to use that comes from chaining .then, .catch, or .finally must also be cached or stable between renders; each call returns a new Promise.

Usage {/usage/}

Reading context with use {/reading-context-with-use/}

When a context is passed to use, it works similarly to useContext. While useContext must be called at the top level of your component, use can be called inside conditionals like if and loops like for. use is preferred over useContext because it is more flexible.

import { use } from 'react';

function Button() {
  const theme = use(ThemeContext);
  // ... 

use returns the context value for the context you passed. To determine the context value, React searches the component tree and finds the closest context provider above for that particular context.

To pass context to a Button, wrap it or one of its parent components into the corresponding context provider.

function MyPage() {
  return (
    <ThemeContext value="dark">
      <Form />
    </ThemeContext>
  );
}

function Form() {
  // ... renders buttons inside ...
}

It doesn't matter how many layers of components there are between the provider and the Button. When a Button anywhere inside of Form calls use(ThemeContext), it will receive "dark" as the value.

Unlike useContext, use can be called in conditionals and loops like if.

function HorizontalRule({ show }) {
  if (show) {
    const theme = use(ThemeContext);
    return <hr className={theme} />;
  }
  return false;
}

use is called from inside a if statement, allowing you to conditionally read values from a Context.

Like useContext, use(context) always looks for the closest context provider above the component that calls it. It searches upwards and does not consider context providers in the component from which you're calling use(context).

import { createContext, use } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
  return (
    <ThemeContext value="dark">
      <Form />
    </ThemeContext>
  )
}

function Form() {
  return (
    <Panel title="Welcome">
      <Button show={true}>Sign up</Button>
      <Button show={false}>Log in</Button>
    </Panel>
  );
}

function Panel({ title, children }) {
  const theme = use(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

function Button({ show, children }) {
  if (show) {
    const theme = use(ThemeContext);
    const className = 'button-' + theme;
    return (
      <button className={className}>
        {children}
      </button>
    );
  }
  return false
}
.panel-light,
.panel-dark {
  border: 1px solid black;
  border-radius: 4px;
  padding: 20px;
}
.panel-light {
  color: #222;
  background: #fff;
}

.panel-dark {
  color: #fff;
  background: rgb(23, 32, 42);
}

.button-light,
.button-dark {
  border: 1px solid #777;
  padding: 5px;
  margin-right: 10px;
  margin-top: 10px;
}

.button-dark {
  background: #222;
  color: #fff;
}

.button-light {
  background: #fff;
  color: #222;
}

Streaming data from the server to the client {/streaming-data-from-server-to-client/}

Data can be streamed from the server to the client by passing a Promise as a prop from a Server Component to a Client Component.

import { fetchMessage } from './lib.js';
import { Message } from './message.js';

export default function App() {
  const messagePromise = fetchMessage();
  return (
    <Suspense fallback={<p>waiting for message...</p>}>
      <Message messagePromise={messagePromise} />
    </Suspense>
  );
}

The Client Component then takes the Promise it received as a prop and passes it to the use API. This allows the Client Component to read the value from the Promise that was initially created by the Server Component.

// message.js
'use client';

import { use } from 'react';

export function Message({ messagePromise }) {
  const messageContent = use(messagePromise);
  return <p>Here is the message: {messageContent}</p>;
}

Because Message is wrapped in Suspense, the fallback will be displayed until the Promise is resolved. When the Promise is resolved, the value will be read by the use API and the Message component will replace the Suspense fallback.

"use client";

import { use, Suspense } from "react";

function Message({ messagePromise }) {
  const messageContent = use(messagePromise);
  return <p>Here is the message: {messageContent}</p>;
}

export function MessageContainer({ messagePromise }) {
  return (
    <Suspense fallback={<p>⌛Downloading message...</p>}>
      <Message messagePromise={messagePromise} />
    </Suspense>
  );
}
import { useState } from "react";
import { MessageContainer } from "./message.js";

function fetchMessage() {
  return new Promise((resolve) => setTimeout(resolve, 1000, "⚛️"));
}

export default function App() {
  const [messagePromise, setMessagePromise] = useState(null);
  const [show, setShow] = useState(false);
  function download() {
    setMessagePromise(fetchMessage());
    setShow(true);
  }

  if (show) {
    return <MessageContainer messagePromise={messagePromise} />;
  } else {
    return <button onClick={download}>Download message</button>;
  }
}
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';

// TODO: update this example to use
// the Codesandbox Server Component
// demo environment once it is created
import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

When passing a Promise from a Server Component to a Client Component, its resolved value must be serializable to pass between server and client. Data types like functions aren't serializable and cannot be the resolved value of such a Promise.

Should I resolve a Promise in a Server or Client Component? {/resolve-promise-in-server-or-client-component/}

A Promise can be passed from a Server Component to a Client Component and resolved in the Client Component with the use API. You can also resolve the Promise in a Server Component with await and pass the required data to the Client Component as a prop.

export default async function App() {
  const messageContent = await fetchMessage();
  return <Message messageContent={messageContent} />
}

But using await in a Server Component will block its rendering until the await statement is finished. Passing a Promise from a Server Component to a Client Component prevents the Promise from blocking the rendering of the Server Component.

Using Promises in Client Components {/using-promises-in-client-components/}

When you pass a Promise to use that was created in a Client Component, it must be cached or stable between renders. One way to do that is to memoize the async work by input—for example, a cache keyed by the same arguments returns the same Promise for the same arguments. In this example, getDoubleCountCached(count) returns the same Promise for a given count, so the component does not re-suspend when re-rendering with the same count.

import { use } from 'react';

export default function DoubleCount({ count }) {
  const doubleCount = use(getDoubleCountCached(count));

  return (
    <div>
      <p>Count: {count}</p>
      <p>Double count: {doubleCount}</p>
    </div>
  );
}

function getDoubleCount(count) {
  return new Promise((resolve) =>
    setTimeout(() => resolve(count * 2), 500)
  );
}

function cacheFn(fn) {
  const cacheMap = new Map();
  return (...args) => {
    const key = JSON.stringify(args);
    if (cacheMap.has(key)) {
      return cacheMap.get(key);
    }
    const r = fn(...args);
    cacheMap.set(key, r);
    return r;
  };
}

const getDoubleCountCached = cacheFn(getDoubleCount);
import { useState, Suspense } from 'react';
import DoubleCount from './DoubleCount.js';

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button
        onClick={() => {
          setCount((c) => c + 1);
        }}
      >
        Increment
      </button>
      <button
        onClick={() => {
          setCount((c) => c - 1);
        }}
      >
        Decrement
      </button>
      <Suspense fallback={<p>🌀 Loading...</p>}>
        <DoubleCount count={count} />
      </Suspense>
    </div>
  );
}
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);
Using an uncached Promise in a Client Component keeps the app in the loading state. {/pitfall-uncached-client-promise/}

If you pass getDoubleCount(count) instead of getDoubleCountCached(count) to use, a new Promise is created on every render. React treats it as a new resource each time, so the component suspends again and the Suspense fallback stays visible. The app will appear stuck on the loading state (or flicker between loading and content). Always cache or otherwise stabilize client-created Promises passed to use.

// ❌ New Promise every render — component re-suspends each time
const doubleCount = use(getDoubleCount(count));

// ✅ Same Promise for same count — suspends once per count
const doubleCount = use(getDoubleCountCached(count));
Chained Promises (.then, .catch, .finally) must be cached too. {/pitfall-chained-promise/}

Each call to .then, .catch, or .finally returns a new Promise. If you pass that chained Promise to use without caching it, you get a new Promise every render and the component will re-suspend each time.

// ❌ New Promise every render — .then() returns a new Promise each time
const data = use(fetch(url).then((r) => r.json()));

const fetchJsonCached = (() => {
  const cache = new Map();
  return (url) => {
    if (!cache.has(url)) {
      cache.set(url, fetch(url).then((r) => r.json()));
    }
    return cache.get(url);
  };
})();

function MyComponent() {
  // ✅ Cache the chained Promise so the same reference is used for the same url
  const data = use(fetchJsonCached('/api/my-api'));

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

Dealing with rejected Promises {/dealing-with-rejected-promises/}

In some cases a Promise passed to use could be rejected. You can handle rejected Promises by either:

  1. Displaying an error to users with an Error Boundary.
  2. Providing an alternative value with Promise.catch
`use` cannot be called in a try-catch block. Instead of a try-catch block [wrap your component in an Error Boundary](#displaying-an-error-to-users-with-error-boundary), or [provide an alternative value to use with the Promise's `.catch` method](#providing-an-alternative-value-with-promise-catch).

Displaying an error to users with an Error Boundary {/displaying-an-error-to-users-with-error-boundary/}

If you'd like to display an error to your users when a Promise is rejected, you can use an Error Boundary. To use an Error Boundary, wrap the component where you are calling the use API in an Error Boundary. If the Promise passed to use is rejected the fallback for the Error Boundary will be displayed.

"use client";

import { use, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";

export function MessageContainer({ messagePromise }) {
  return (
    <ErrorBoundary fallback={<p>⚠️Something went wrong</p>}>
      <Suspense fallback={<p>⌛Downloading message...</p>}>
        <Message messagePromise={messagePromise} />
      </Suspense>
    </ErrorBoundary>
  );
}

function Message({ messagePromise }) {
  const content = use(messagePromise);
  return <p>Here is the message: {content}</p>;
}
import { useState } from "react";
import { MessageContainer } from "./message.js";

function fetchMessage() {
  return new Promise((resolve, reject) => setTimeout(reject, 1000));
}

export default function App() {
  const [messagePromise, setMessagePromise] = useState(null);
  const [show, setShow] = useState(false);
  function download() {
    setMessagePromise(fetchMessage());
    setShow(true);
  }

  if (show) {
    return <MessageContainer messagePromise={messagePromise} />;
  } else {
    return <button onClick={download}>Download message</button>;
  }
}
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';

// TODO: update this example to use
// the Codesandbox Server Component
// demo environment once it is created
import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);
{
  "dependencies": {
    "react": "19.0.0",
    "react-dom": "19.0.0",
    "react-scripts": "^5.0.0",
    "react-error-boundary": "4.0.3"
  },
  "main": "/index.js"
}

Providing an alternative value with Promise.catch {/providing-an-alternative-value-with-promise-catch/}

If you'd like to provide an alternative value when the Promise passed to use is rejected you can use the Promise's catch method.

import { Message } from './message.js';

export default function App() {
  const messagePromise = new Promise((resolve, reject) => {
    reject();
  }).catch(() => {
    return "no new message found.";
  });

  return (
    <Suspense fallback={<p>waiting for message...</p>}>
      <Message messagePromise={messagePromise} />
    </Suspense>
  );
}

To use the Promise's catch method, call catch on the Promise object. catch takes a single argument: a function that takes an error message as an argument. Whatever is returned by the function passed to catch will be used as the resolved value of the Promise.


Troubleshooting {/troubleshooting/}

My component stays on the loading state (or keeps flickering) when I use a Promise with use {/promise-not-stable/}

The Promise you pass to use is likely recreated on every render. React treats a new Promise as a new resource, so the component suspends again each time and the Suspense fallback stays visible (or the UI flickers between fallback and content). This often happens when the Promise is created inside a Client Component without being cached or stored, or when you pass the result of .then(), .catch(), or .finally()—each call returns a new Promise, so that chained result must be cached too.

// ❌ New Promise every render — component re-suspends each time
function MyComponent({ id }) {
  const data = use(fetchData(id)); // fetchData(id) returns a new Promise each render
  return <p>{data}</p>;
}

Cache or otherwise stabilize the Promise between renders. For example, store it in state, or use a cache keyed by the same inputs so the same Promise is returned for the same arguments. See the Client Component caching example.

// ✅ Same Promise for same id — cache returns stable reference per argument
const promiseCache = new Map();
function fetchDataCached(id) {
  if (!promiseCache.has(id)) promiseCache.set(id, fetchData(id));
  return promiseCache.get(id);
}

function MyComponent({ id }) {
  const data = use(fetchDataCached(id));
  return <p>{data}</p>;
}

"Suspense Exception: This is not a real error!" {/suspense-exception-error/}

You are either calling use outside of a React Component or Hook function, or calling use in a try–catch block. If you are calling use inside a try–catch block, wrap your component in an Error Boundary, or call the Promise's catch to catch the error and resolve the Promise with another value. See these examples.

If you are calling use outside a React Component or Hook function, move the use call to a React Component or Hook function.

function MessageComponent({messagePromise}) {
  function download() {
    // ❌ the function calling `use` is not a Component or Hook
    const message = use(messagePromise);
    // ...

Instead, call use outside any component closures, where the function that calls use is a Component or Hook.

function MessageComponent({messagePromise}) {
  // ✅ `use` is being called from a component. 
  const message = use(messagePromise);
  // ...