use

use는 Promise나 Context와 같은 데이터를 참조하는 React API입니다.

const value = use(resource);

레퍼런스

use(context)

컴포넌트에서 Promise나 Context와 같은 데이터를 참조하려면 use를 사용하세요.

import { use } from 'react';

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

다른 React Hook과 달리 use는 if와 같은 조건문과 반복문 내부에서 호출할 수 있습니다. 다만, 다른 React Hook과 같이 use는 컴포넌트 또는 Hook에서만 호출해야 합니다.

Promise와 함께 호출될 때 use API는 Suspense 및 Error Boundary와 통합됩니다. use에 전달된 Promise가 대기Pending하는 동안 use를 호출하는 컴포넌트는 Suspend됩니다. use를 호출하는 컴포넌트가 Suspense 경계로 둘러싸여 있으면 Fallback이 표시됩니다. Promise가 리졸브되면 Suspense Fallback은 use API가 반환한 컴포넌트로 대체됩니다. use에 전달된 Promise가 Reject되면 가장 가까운 Error Boundary의 Fallback이 표시됩니다.

아래 예시를 참고하세요.

매개변수

  • resource: 참조하려는 데이터입니다. 데이터는 Promise나 Context일 수 있습니다.

반환값

use Hook은 Promise나 Context에서 참조한 값을 반환합니다.

주의 사항

  • use API는 컴포넌트나 Hook 내부에서 호출되어야 합니다.
  • 서버 컴포넌트에서 데이터를 가져올 때는 use보다 async 및 await을 사용합니다. async 및 await은 await이 호출된 시점부터 렌더링을 시작하는 반면, use는 데이터가 리졸브된 후 컴포넌트를 리렌더링합니다.
  • 클라이언트 컴포넌트에서 Promise를 생성하는 것보다 서버 컴포넌트에서 Promise를 생성하여 클라이언트 컴포넌트에 전달하는 것이 좋습니다. 클라이언트 컴포넌트에서 생성된 Promise는 렌더링할 때마다 다시 생성됩니다. 서버 컴포넌트에서 클라이언트 컴포넌트로 전달된 Promise는 리렌더링 전반에 걸쳐 안정적입니다. 예시를 확인하세요.

use(browser())

Call use with the value returned by browser in a component that should only render in the browser:

import { use } from 'react';
import { browser } from 'react-dom';

function BrowserOnly() {
use(browser('This component requires browser APIs.'));
return <BrowserContent />;
}

During server rendering, the component calling use(browser()) suspends and React includes the closest <Suspense> boundary’s fallback in the HTML. In the browser, use(browser()) returns undefined, so the component renders normally.

See an example below.

Parameters

  • browserValue: The value returned by browser.

Returns

use(browser()) returns undefined in the browser.

Caveats

  • The component calling use(browser()) must be inside a <Suspense> boundary during server rendering. Without one, server rendering fails.
  • In a React Server Components app, use(browser()) must be called from a Client Component, not a Server Component.

사용법

use를 사용하여 Context 참조하기

Context가 use에 전달되면 useContext와 유사하게 작동합니다. useContext는 컴포넌트의 최상위 수준에서 호출해야 하지만, use는 if와 같은 조건문이나 for와 같은 반복문 내부에서 호출할 수 있습니다. use는 유연하므로 useContext보다 선호됩니다.

import { use } from 'react';

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

use는 전달한 Context의 Context Value를 반환합니다. Context 값을 결정하기 위해 React는 컴포넌트 트리를 탐색하고 위에서 가장 가까운 Context Provider를 찾습니다.

Context를 Button에 전달하려면 Button 또는 상위 컴포넌트 중 하나를 Context Provider로 래핑합니다.

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

function Form() {
// ... 버튼 렌더링 ...
}

Provider와 Button 사이에 얼마나 많은 컴포넌트가 있는지는 중요하지 않습니다. Form 내부의 어느 곳이든 Button이 use(ThemeContext)를 호출하면 "dark"를 값으로 받습니다.

useContext와 달리, use는 if와 같은 조건문과 반복문 내부에서 호출할 수 있습니다.

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

use는 if 내부에서 호출되므로 Context에서 조건부로 값을 참조할 수 있습니다.

주의하세요!

useContext와 마찬가지로, use(context)는 항상 이를 호출하는 컴포넌트의 위쪽에서 가장 가까운 Context Provider를 찾습니다. 위쪽으로 탐색하며, use(context)를 호출하는 컴포넌트 내부의 Context Provider는 고려하지 않습니다.

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
}

Context에서 Promise 읽기

Prop drilling 없이 비동기 데이터를 공유하려면 Promise를 Context 값으로 설정한 다음 use(context)로 읽고 use(promise)로 리졸브합니다.

import { use } from 'react';
import { UserContext } from './UserContext';

function Profile() {
const userPromise = use(UserContext);
const user = use(userPromise);
return <h1>{user.name}</h1>;
}

Reading the value requires two use calls because the context value itself isn’t awaited. See Before you use context for alternatives to consider before reaching for context.

Wrap the components that read the Promise in a Suspense boundary so only that subtree suspends while the Promise is pending. See Usage (Promises) below for more on reading Promises with use.

주의하세요!

When this pattern is used with Server Components, refetching the Promise requires refetching the Server Component that sets the Promise in context. Avoid setting the Promise in context high in the tree, since that would refetch large parts of the app unnecessarily.


Usage (Promises)

Reading a Promise with use

Call use with a Promise to read its resolved value. The component will suspend while the Promise is pending.

import { use } from 'react';

function Albums({ albumsPromise }) {
const albums = use(albumsPromise);
return (
<ul>
{albums.map(album => (
<li key={album.id}>
{album.title} ({album.year})
</li>
))}
</ul>
);
}

Wrap the component that calls use in a Suspense boundary so React can show a fallback while the Promise is pending. The closest Suspense boundary above the suspending component shows its fallback. Once the Promise resolves, React reads the value with use and replaces the fallback with the rendered component.

Reading a Promise with use vs fetching in an Effect

예시 1 of 2:
Fetching data with use

In this example, Albums calls use with a cached Promise. The component suspends while the Promise is pending, and React displays the nearest Suspense fallback. Rejected Promises propagate to the nearest Error Boundary.

import { use, Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { fetchData } from './data.js';

export default function App() {
  return (
    <ErrorBoundary fallback={<p>Could not fetch albums.</p>}>
      <Suspense fallback={<Loading />}>
        <Albums />
      </Suspense>
    </ErrorBoundary>
  );
}

function Albums() {
  const albums = use(fetchData('/albums'));
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}

function Loading() {
  return <h2>Loading...</h2>;
}

주의하세요!

Promises passed to use must be cached

Promises created during render are recreated on every render, which causes React to show the Suspense fallback repeatedly and prevents content from appearing.

function Albums() {
// 🔴 `fetch` creates a new Promise on every render.
const albums = use(fetch('/albums'));
// ...
}

Instead, pass a Promise from a cache, a Suspense-enabled framework, or a Server Component:

// ✅ fetchData reads the Promise from a cache.
const albums = use(fetchData('/albums'));
자세히 살펴보기

Why are Promises recreated on every render?

React doesn’t preserve state for renders that suspended before mounting. After each suspension, React retries rendering from scratch, so any Promise created during render is recreated.

Common ways a Promise can be unintentionally recreated during render:

function Albums() {
// 🔴 `fetch` creates a new Promise on every render.
const albums = use(fetch('/albums'));

// 🔴 Uncached `async` function calls create a new Promise on every render.
const albums = use((async () => {
const res = await fetch('/albums');
return res.json();
})());

// 🔴 Adding `.then` returns a new Promise on every render,
// even if `fetchData` is cached.
const albums = use(fetchData('/albums').then(res => res.json()));
// ...
}

Ideally, Promises are created before rendering, such as in an event handler, a route loader, or a Server Component, and passed to the component that calls use. Fetching lazily in render delays network requests and can create waterfalls.

// ✅ fetchData reads the Promise from a cache.
const albums = use(fetchData('/albums'));

Caching Promises for Client Components

Promises passed to use in Client Components must be cached so the same Promise instance is reused across re-renders. If a new Promise is created directly in render, React will display the Suspense fallback on every re-render.

// ✅ Cache the Promise so the same one is reused across renders
let cache = new Map();

export function fetchData(url) {
if (!cache.has(url)) {
cache.set(url, getData(url));
}
return cache.get(url);
}

The fetchData function returns the same Promise each time it’s called with the same URL. When use receives the same Promise on a re-render, it reads the already-resolved value synchronously without suspending.

중요합니다!

The way you cache Promises depends on the framework you use with Suspense. Frameworks typically provide built-in caching mechanisms. If you don’t use a framework, you can use a simple module-level cache like the one above, or a Suspense-enabled data source.

In the example below, clicking “Re-render” updates state in App and triggers a re-render. Because fetchData returns the same cached Promise, Albums reads the value synchronously instead of showing the Suspense fallback again.

import { use, Suspense, useState } from 'react';
import { fetchData } from './data.js';

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        Re-render
      </button>
      <p>Render count: {count}</p>
      <Suspense fallback={<p>Loading...</p>}>
        <Albums />
      </Suspense>
    </>
  );
}

function Albums() {
  const albums = use(fetchData('/albums'));
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}

자세히 살펴보기

How to implement a promise cache

A basic cache stores the Promise keyed by URL so the same instance is reused across renders. To also avoid unnecessary Suspense fallbacks when data is already available, you can set status and value (or reason) fields on the Promise. React checks these fields when use is called: if status is 'fulfilled', it reads value synchronously without suspending. If status is 'rejected', it throws reason. If the field is missing or 'pending', it suspends.

let cache = new Map();

function fetchData(url) {
if (!cache.has(url)) {
const promise = getData(url);
promise.status = 'pending';
promise.then(
value => {
promise.status = 'fulfilled';
promise.value = value;
},
reason => {
promise.status = 'rejected';
promise.reason = reason;
},
);
cache.set(url, promise);
}
return cache.get(url);
}

This is primarily useful for library authors building Suspense-compatible data layers. React will set the status field itself on Promises that don’t have it, but setting it yourself avoids an extra render when the data is already available.

This cache pattern is the foundation for re-fetching data (where changing the cache key triggers a new fetch) and preloading data on hover (where calling fetchData early means the Promise may already be resolved by the time use reads it).

주의하세요!

Don’t skip calling use based on whether a Promise is already settled.

Unlike other hooks, use can be called inside conditions and loops — but it must always be called for the Promise itself. Never read promise.status or promise.value directly to bypass use; always pass the Promise to use and let React handle it.

// 🔴 Don't bypass `use` by reading promise status directly
if (promise.status === 'fulfilled') {
return promise.value;
}
const value = use(promise);
// ✅ Pass the promise to `use` and let React track the promise
const value = use(promise);

Bypassing use this way can break React Suspense optimizations and Suspense features for React DevTools. You can use(promise) conditionally, but don’t conditionally use(promise) based on the promise itself.


Re-fetching data in Client Components

To refresh data at the same URL (for example, with a “Refresh” button), invalidate the cache entry and start a new fetch inside a startTransition. Store the resulting Promise in state to trigger a re-render. While the new Promise is pending, React keeps showing the existing content because the update is inside a Transition.

function App() {
const [albumsPromise, setAlbumsPromise] = useState(fetchData('/albums'));
const [isPending, startTransition] = useTransition();

function handleRefresh() {
startTransition(() => {
setAlbumsPromise(refetchData('/albums'));
});
}
// ...
}

refetchData clears the old cache entry and starts a new fetch at the same URL. Storing the resulting Promise in state triggers a re-render inside the Transition. On re-render, Albums receives the new Promise and use suspends on it while React keeps showing the old content.

import { Suspense, useState, useTransition } from 'react';
import { use } from 'react';
import { fetchData, refetchData } from './data.js';

export default function App() {
  const [albumsPromise, setAlbumsPromise] = useState(
    () => fetchData('/the-beatles/albums')
  );
  const [isPending, startTransition] = useTransition();

  function handleRefresh() {
    startTransition(() => {
      setAlbumsPromise(refetchData('/the-beatles/albums'));
    });
  }

  return (
    <>
      <button
        onClick={handleRefresh}
        disabled={isPending}
      >
        {isPending ? 'Refreshing...' : 'Refresh'}
      </button>
      <div style={{ opacity: isPending ? 0.6 : 1 }}>
        <Suspense fallback={<Loading />}>
          <Albums albumsPromise={albumsPromise} />
        </Suspense>
      </div>
    </>
  );
}

function Albums({ albumsPromise }) {
  const albums = use(albumsPromise);
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}

function Loading() {
  return <h2>Loading...</h2>;
}

중요합니다!

Frameworks that support Suspense typically provide their own caching and invalidation mechanisms. The custom cache above is useful for understanding the pattern, but in practice prefer your framework’s data fetching solution.


Preloading data on hover

You can start loading data before it’s needed by calling fetchData during a hover event. Since fetchData caches the Promise, the data may already be available by the time the user clicks. If the Promise has resolved by the time use reads it, React renders the component immediately without showing a Suspense fallback.

<button
onMouseEnter={() => fetchData(`/${id}/albums`)}
onClick={() => {
startTransition(() => {
setArtistId(id);
});
}}
>

In this example, hovering over an artist button starts fetching their albums in the background. Without hovering first, clicking shows a loading fallback. Try hovering over a button for a moment before clicking to see the difference.

import { Suspense, useState, useTransition } from 'react';
import Albums from './Albums.js';
import { fetchData } from './data.js';

export default function App() {
  const [artistId, setArtistId] = useState('the-beatles');
  const [isPending, startTransition] = useTransition();

  return (
    <>
      <div>
        {['the-beatles', 'led-zeppelin', 'pink-floyd'].map(id => (
          <button
            key={id}
            onMouseEnter={() => {
              fetchData(`/${id}/albums`);
            }}
            onClick={() => {
              startTransition(() => {
                setArtistId(id);
              });
            }}
          >
            {id === 'the-beatles' ? 'The Beatles' :
             id === 'led-zeppelin' ? 'Led Zeppelin' :
             'Pink Floyd'}
          </button>
        ))}
      </div>
      <Suspense key={artistId} fallback={<Loading />}>
        <Albums artistId={artistId} />
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>Loading...</h2>;
}


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

클라이언트 컴포넌트는 Prop으로 받은 Promise를 use API에 전달합니다. 클라이언트 컴포넌트는 서버 컴포넌트가 처음에 생성한 Promise에서 값을 읽을 수 있습니다.

// message.js
'use client';

import { use } from 'react';

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

Message는 Suspense로 래핑되어 있으므로 Promise가 리졸브될 때까지 Fallback이 표시됩니다. Promise가 리졸브되면 use Hook이 값을 참조하고 Message 컴포넌트가 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>
  );
}

중요합니다!

서버 컴포넌트에서 클라이언트 컴포넌트로 Promise를 전달할 때 리졸브된 값이 직렬화 가능해야 합니다. 함수는 직렬화할 수 없으므로 Promise의 리졸브 값이 될 수 없습니다.

자세히 살펴보기

Promise를 서버 컴포넌트에서 처리해야 하나요, 아니면 클라이언트 컴포넌트에서 처리해야 하나요?

Promise는 서버 컴포넌트에서 클라이언트 컴포넌트로 전달할 수 있으며 use API를 통해 클라이언트 컴포넌트에서 리졸브됩니다. 또한 서버 컴포넌트에서 await을 사용하여 Promise를 리졸브하고 데이터를 클라이언트 컴포넌트에 Prop으로 전달하는 방법도 존재합니다.

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

하지만 서버 컴포넌트에서 await을 사용하면 await 문이 완료될 때까지 렌더링이 차단됩니다. 서버 컴포넌트에서 클라이언트 컴포넌트로 Promise를 Prop으로 전달하면 Promise가 서버 컴포넌트의 렌더링을 차단하는 것을 방지할 수 있습니다.

거부된 Promise 처리하기

경우에 따라 use에 전달된 Promise가 거부될 수 있습니다. 거부된 프로미스를 처리하는 방법은 2가지가 존재합니다.

  1. Error Boundary를 사용하여 오류 표시하기
  2. Promise.catch로 대체 값 제공하기

주의하세요!

use는 try-catch 블록에서 호출할 수 없습니다. try-catch 블록 대신 컴포넌트를 Error Boundary로 래핑하거나, Promise의 catch 메서드를 사용하여 대체 값을 제공해야 합니다.

Error Boundary를 사용하여 오류 표시하기

Promise가 거부될 때 오류를 표시하고 싶다면 Error Boundary를 사용합니다. Error Boundary를 사용하려면 use API 를 호출하는 컴포넌트를 Error Boundary로 래핑합니다. use에 전달된 Promise가 거부되면 Error Boundary에 대한 Fallback이 표시됩니다.

import { use, Suspense, useState, startTransition } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { fetchData, refetchData } from "./data.js";

export default function App() {
  const [albumsPromise, setAlbumsPromise] = useState(
    () => fetchData('/the-beatles/albums')
  );

  function handleRetry() {
    startTransition(() => {
      setAlbumsPromise(refetchData('/the-beatles/albums'));
    });
  }

  return (
    <ErrorBoundary
      resetKeys={[albumsPromise]}
      fallbackRender={() => (
        <>
          <p>⚠️ Something went wrong loading the albums.</p>
          <button onClick={handleRetry}>Try again</button>
        </>
      )}
    >
      <Suspense fallback={<p>Loading...</p>}>
        <Albums albumsPromise={albumsPromise} />
      </Suspense>
    </ErrorBoundary>
  );
}

function Albums({ albumsPromise }) {
  const albums = use(albumsPromise);
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}

Promise.catch로 대체 값 제공하기

use에 전달된 Promise가 거부될 때 대체 값을 제공하려면 Promise의 catch 메서드를 사용합니다.

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

Promise의 catch 메서드를 사용하려면 Promise 객체에서 catch를 호출합니다. catch는 오류 메시지를 인수로 받는 함수를 인수로 받습니다. catch에 전달된 함수가 반환하는 값은 모두 Promise의 리졸브 값으로 사용됩니다.


브라우저 사용법

Rendering a component only in the browser

Pass the value returned by browser to use inside a component that should only render in the browser.

Click Reload to see the loading fallback in the initial HTML. After hydration, React displays the draft loaded from localStorage.

import { Suspense, use, useState } from 'react';
import { browser } from 'react-dom';

function SavedDraft() {
  use(browser('The draft is stored in localStorage.'));
  const [draft, setDraft] = useState(
    () => localStorage.getItem('draft') ?? ''
  );

  function handleChange(event) {
    const nextDraft = event.target.value;
    setDraft(nextDraft);
    localStorage.setItem('draft', nextDraft);
  }

  return (
    <label>
      Draft:
      <textarea
        value={draft}
        onChange={handleChange}
        rows={4}
        cols={30}
      />
    </label>
  );
}

export default function App() {
  return (
    <>
      <h1>Saved draft</h1>
      <Suspense fallback={<p>Loading draft...</p>}>
        <SavedDraft />
      </Suspense>
    </>
  );
}

During server rendering, use(browser()) suspends the component and React includes the closest Suspense boundary’s fallback in the HTML. In the browser, use(browser()) returns undefined and the saved draft renders normally.


문제 해결

I’m getting an error: “Suspense Exception: This is not a real error!”

React 컴포넌트 또는 Hook 함수 외부에서, 혹은 try-catch 블록에서 use를 호출하고 있는 경우입니다. try-catch 블록 내에서 use를 호출하는 경우 컴포넌트를 Error Boundary로 래핑하거나 Promise의 catch를 호출하여 오류를 발견하고 Promise를 다른 값으로 리졸브합니다. 이러한 예시들을 확인하세요.

function MessageComponent({messagePromise}) {
function download() {
// ❌ `use`를 호출하는 함수가 컴포넌트나 Hook이 아닙니다.
const message = use(messagePromise);
// ...

대신, 컴포넌트 클로저 외부에서 use를 호출하세요. 여기서 use를 호출하는 함수는 컴포넌트 또는 Hook입니다.

function MessageComponent({messagePromise}) {
// ✅ `use`를 컴포넌트에서 호출하고 있습니다.
const message = use(messagePromise);
// ...

Instead, wrap the component in an Error Boundary:

function Albums({ albumsPromise }) {
// ✅ Call `use` without try-catch
const albums = use(albumsPromise);
// ...
// ✅ Use an Error Boundary to handle errors
<ErrorBoundary fallback={<p>Error</p>}>
<Albums albumsPromise={albumsPromise} />
</ErrorBoundary>

I’m getting a warning: “A component was suspended by an uncached promise”

The Promise passed to use is not cached, so React cannot reuse it across re-renders.

This commonly happens when calling fetch or an async function directly in render:

function Albums() {
// 🔴 This creates a new Promise on every render
const albums = use(fetch('/albums'));
// ...
}

To fix this, cache the Promise so the same instance is reused:

// ✅ fetchData returns the same Promise for the same URL
const albums = use(fetchData('/albums'));

See caching Promises for Client Components for more details.