레퍼런스
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이 표시됩니다.
매개변수
반환값
use Hook은 Promise나 Context에서 참조한 값을 반환합니다.
주의 사항
useAPI는 컴포넌트나 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.
Parameters
browserValue: The value returned bybrowser.
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에서 조건부로 값을 참조할 수 있습니다.
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.
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.
예시 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>; }
자세히 살펴보기
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.
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> ); }
자세히 살펴보기
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).
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>; }
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는 서버 컴포넌트에서 클라이언트 컴포넌트로 전달할 수 있으며 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가지가 존재합니다.
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.