use はプロミス (Promise) やコンテクストなどのリソースをレンダー中に読み取るための React API です。
const value = use(resource);リファレンス
use(context)
コンテクストを指定して use を呼び出し、その値を読み取ります。useContext とは異なり、use はループや if のような条件文の中でも呼び出せます。
import { use } from 'react';
function Button() {
const theme = use(ThemeContext);
// ...引数
context:createContextで作成したコンテクスト。
返り値
渡されたコンテクストの値です。この値は、呼び出し元コンポーネントより上にある最も近いコンテクストプロバイダによって決まります。プロバイダがない場合は、createContext に渡した defaultValue が返されます。
注意点
useはコンポーネントまたはフックの内部で呼び出す必要があります。- サーバコンポーネントでは、
useによるコンテクストの読み取りはサポートされていません。
use(promise)
プロミスを指定して use を呼び出し、その解決値を読み取ります。プロミスが保留中の間、use を呼び出したコンポーネントはサスペンドします。その名前に反して、use はフックではありません。フックとは異なり、ループや if のような条件文の中でも呼び出せます。
import { use } from 'react';
function MessageComponent({ messagePromise }) {
const message = use(messagePromise);
// ...use を呼び出すコンポーネントがサスペンスバウンダリでラップされている場合、プロミスが保留中の間はフォールバックが表示されます。プロミスが解決されると、サスペンスのフォールバックは、use が返したデータを使用してレンダーされたコンポーネントに置き換わります。プロミスが拒否された場合は、最も近いエラーバウンダリ (Error Boundary) のフォールバックが表示されます。
引数
返り値
プロミスの解決値です。
注意点
useはコンポーネントまたはフックの内部で呼び出す必要があります。useを try-catch ブロック内で呼び出すことはできません。代わりに、コンポーネントをエラーバウンダリでラップしてエラーをキャッチし、フォールバックを表示します。useに渡すプロミスは、再レンダーをまたいで同じプロミスインスタンスが再利用されるようにキャッシュされている必要があります。以下のプロミスのキャッシュに関する説明を参照してください。- サーバコンポーネントからクライアントコンポーネントにプロミスを渡す場合、その解決後の値はシリアライズ可能でなければなりません。
Canary only use(browser())
ブラウザでのみレンダーされるべきコンポーネントで、browser が返した値を指定して use を呼び出します。
import { use } from 'react';
import { browser } from 'react-dom';
function BrowserOnly() {
use(browser('This component requires browser APIs.'));
return <BrowserContent />;
}サーバレンダリング中は、use(browser()) を呼び出したコンポーネントがサスペンドし、React は最も近い <Suspense> バウンダリのフォールバックを HTML に含めます。ブラウザでは use(browser()) が undefined を返すため、コンポーネントは通常どおりレンダーされます。
引数
browserValue:browserが返す値です。
返り値
ブラウザでは、use(browser()) は undefined を返します。
注意点
- サーバレンダリング中、
use(browser())を呼び出すコンポーネントは<Suspense>バウンダリの内部になければなりません。バウンダリがなければ、サーバレンダリングは失敗します。 - React サーバコンポーネントのアプリでは、
use(browser())はクライアントコンポーネントから呼び出す必要があり、サーバコンポーネントからは呼び出せません。
使用法(コンテクスト)
use でコンテクストを読み取る
コンテクストが use に渡された場合、useContext と同様に動作します。useContext はコンポーネントのトップレベルで呼び出す必要がありますが、use は if のような条件分岐や for のようなループの中でも呼び出すことができます。
import { use } from 'react';
function Button() {
const theme = use(ThemeContext);
// ...use は、渡したコンテクストの値を返します。コンテクストの値を決定するために、React はコンポーネントツリーを上方向に検索し、当該コンテクストに対応する最も近いコンテクストプロバイダ (context provider) を見つけます。
Button にコンテクストを渡すには、それまたはその親コンポーネントのいずれかを、対応するコンテクストプロバイダでラップします。
function MyPage() {
return (
<ThemeContext value="dark">
<Form />
</ThemeContext>
);
}
function Form() {
// ... renders buttons inside ...
}プロバイダと 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 文の中から呼び出さているため、条件付きでコンテクストから値を読み取ることができます。
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 }
コンテクストからプロミスを読み取る
props の穴掘り作業 (prop drilling) をせずに非同期データを共有するには、プロミスをコンテクスト値として設定し、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>;
}コンテクスト値自体は await されないため、値の読み取りには use を 2 回呼び出す必要があります。コンテクストを使う前に検討すべき代替手段については、コンテクストを使用する前にを参照してください。
プロミスを読み取るコンポーネントをサスペンスバウンダリでラップすると、プロミスが保留中の間はそのサブツリーだけがサスペンドします。use によるプロミスの読み取りについて詳しくは、以下の使用法(プロミス)を参照してください。
使用法(プロミス)
use でプロミスを読み取る
プロミスを指定して use を呼び出し、その解決値を読み取ります。プロミスが保留中の間、コンポーネントはサスペンドします。
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>
);
}use を呼び出すコンポーネントをサスペンスバウンダリでラップすると、プロミスが保留中の間、React はフォールバックを表示できます。サスペンドしたコンポーネントより上にある最も近いサスペンスバウンダリが、そのフォールバックを表示します。プロミスが解決されると、React は use で値を読み取り、フォールバックをレンダーされたコンポーネントに置き換えます。
例 1/2: use でデータフェッチ
この例では、Albums がキャッシュ済みのプロミスを指定して use を呼び出します。プロミスが保留中の間、コンポーネントはサスペンドし、React は最も近いサスペンスのフォールバックを表示します。拒否されたプロミスは、最も近いエラーバウンダリへ伝播します。
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 はマウント前にサスペンドしたレンダーの state を保持しません。サスペンドするたびに React はレンダーを最初からやり直すため、レンダー中に作成したプロミスも再作成されます。
レンダー中に意図せずプロミスが再作成される一般的な例を以下に示します。
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()));
// ...
}理想的には、イベントハンドラ、ルートローダ、サーバコンポーネントなどでレンダー前にプロミスを作成し、use を呼び出すコンポーネントに渡します。レンダー中に遅延フェッチを行うとネットワークリクエストの開始が遅れ、ウォーターフォールが発生する可能性があります。
// ✅ fetchData reads the Promise from a cache.
const albums = use(fetchData('/albums'));クライアントコンポーネント用にプロミスをキャッシュする
クライアントコンポーネントで use に渡すプロミスは、再レンダーをまたいで同じプロミスインスタンスが再利用されるようにキャッシュする必要があります。レンダー内で新しいプロミスを直接作成すると、React は再レンダーのたびにサスペンスのフォールバックを表示します。
// ✅ 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);
}fetchData 関数は、同じ URL で呼び出されるたびに同じプロミスを返します。再レンダー時に use が同じプロミスを受け取ると、サスペンドせずに、すでに解決された値を同期的に読み取ります。
以下の例では、“Re-render” をクリックすると、App の state が更新され、再レンダーがトリガされます。fetchData は同じキャッシュ済みプロミスを返すため、Albums はサスペンスのフォールバックを再び表示することなく、値を同期的に読み取ります。
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> ); }
さらに深く知る
基本的なキャッシュの場合、URL をキーとしてプロミスを保存し、レンダーをまたいで同じインスタンスが再利用されるようにします。データがすでに利用可能な場合にサスペンスの不要なフォールバックが表示されることも避けるには、プロミスに status と value(または reason)フィールドを設定できます。React は use が呼び出されたときにこれらのフィールドを確認します。status が 'fulfilled' なら、サスペンドせずに value を同期的に読み取ります。status が 'rejected' なら reason をスローします。フィールドがないか 'pending' ならサスペンドします。
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);
}これは主に、サスペンス互換のデータレイヤを構築するライブラリ作者にとって有用です。status フィールドがないプロミスには React 自身がこのフィールドを設定しますが、自分で設定しておけば、データがすでに利用可能な場合の余分なレンダーを避けられます。
このキャッシュパターンは、データの再フェッチ(キャッシュキーの変更によって新しいフェッチをトリガする)や、ホバー時のデータのプリロード(早めに fetchData を呼び出すことで、use が読み取る時点ではプロミスがすでに解決している可能性がある)の基礎になります。
クライアントコンポーネントでデータを再フェッチする
同じ URL のデータを更新するには(例えば “Refresh” ボタンを使う場合)、キャッシュエントリを無効化し、startTransition 内で新しいフェッチを開始します。結果のプロミスを state に保存して、再レンダーをトリガします。更新がトランジション内で行われるため、新しいプロミスが保留中の間も React は既存のコンテンツを表示し続けます。
function App() {
const [albumsPromise, setAlbumsPromise] = useState(fetchData('/albums'));
const [isPending, startTransition] = useTransition();
function handleRefresh() {
startTransition(() => {
setAlbumsPromise(refetchData('/albums'));
});
}
// ...
}refetchData は古いキャッシュエントリを削除し、同じ URL への新しいフェッチを開始します。結果のプロミスを state に保存すると、トランジション内で再レンダーがトリガされます。再レンダー時に Albums が新しいプロミスを受け取り、use がそのプロミスに対してサスペンドする間、React は古いコンテンツを表示し続けます。
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>; }
ホバー時にデータをプリロード
ホバーイベント中に fetchData を呼び出すことで、データが必要になる前に読み込みを開始できます。fetchData はプロミスをキャッシュするため、ユーザがクリックする時点でデータをすでに利用可能にできているかもしれません。use が読み取る時点でプロミスが解決済みなら、React はサスペンスのフォールバックを表示せず、すぐにコンポーネントをレンダーします。
<button
onMouseEnter={() => fetchData(`/${id}/albums`)}
onClick={() => {
startTransition(() => {
setArtistId(id);
});
}}
>以下の例では、アーティストのボタンにホバーすると、そのアルバムのバックグラウンドでのフェッチが始まります。先にホバーせずクリックすると、読み込み中のフォールバックが表示されます。違いを確認するため、ボタンをクリックする前にしばらくホバーしてみてください。
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>; }
サーバからクライアントへのデータストリーミング
サーバコンポーネントからクライアントコンポーネントに props としてプロミスを渡すことで、サーバからクライアントにデータをストリーミングすることができます。
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>
);
}クライアントコンポーネントは、受け取ったプロミスを use API に渡します。これによりクライアントコンポーネントは、サーバコンポーネントが最初に作成したプロミスから値を読み取ることができます。
// 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 はサスペンスバウンダリでラップされているため、プロミスが解決されるまでフォールバックが表示されます。プロミスが解決されると、その値が use API によって読み取られ、Message コンポーネントがサスペンスフォールバックを置き換えます。
"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> ); }
さらに深く知る
プロミスがある場合、その値を読み取るには、いずれかの時点でプロミスから値を取り出す必要があります。サーバコンポーネントでは await、クライアントコンポーネントでは use を使って値を取り出します。
通常、最も単純なのはプロミスを作成した場所で await する方法です。データの準備ができるまでサーバコンポーネントがサスペンドし、その配下にあるものもすべて待機します。
// Server Component
export default async function App() {
const messageContent = await fetchMessage();
return <Message messageContent={messageContent} />;
}ただし、すぐに値を取り出す必要はありません。プロミスを props の一部として下に渡し、ツリーのより深い位置で値を取り出せます。プロミスを読み取るコンポーネントはやはりサスペンドしますが、データを待つのはツリーのその部分だけです。そのコンポーネントを <Suspense> バウンダリでラップすると、ページの残りの部分をすぐにレンダーしながらフォールバックを表示できます。
例えば、より深い位置にあるサーバコンポーネントで、受け取ったプロミスを await できます。
import { Suspense } from 'react';
// Server Component
export default function App() {
const messagePromise = fetchMessage();
return (
<Suspense fallback={<p>⌛Downloading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}
// Server Component
async function Message({ messagePromise }) {
const messageContent = await messagePromise;
return <p>{messageContent}</p>;
}または、別ファイル内のクライアントコンポーネントで、同じプロミスから use によって値を取り出せます。
// Client Component
'use client';
import { use } from 'react';
export function Message({ messagePromise }) {
const messageContent = use(messagePromise);
return <p>{messageContent}</p>;
}どちらの場合も、プロミスを下に渡す仕組みは同じです。いずれもプロミスを読み取る場所でサスペンドし、それより上の UI をブロックしません。唯一の違いは、クライアントコンポーネントはレンダー中に await できないため、代わりに use でプロミスから値を取り出すことです。一般的な例として、ホバーやクリックの後でのみデータが必要になるポップオーバーやツールチップなどのインタラクティブなコンテンツがあります。
サスペンスバウンダリを配置する場所については、コンテンツを一度にまとめて表示するを参照してください。
エラーバウンダリでエラーを表示する
use に渡したプロミスが拒否されると、エラーは最も近いエラーバウンダリに伝播します。プロミスが拒否されたときにフォールバックを表示するには、use を呼び出すコンポーネントをエラーバウンダリでラップします。
以下の例では、fetchData は最初の試行では拒否され、再試行すると成功します。エラーバウンダリが拒否をキャッチし、“Try again” ボタンを含むフォールバックを表示します。
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> ); }
使用法(ブラウザ)
Canary only コンポーネントをブラウザでのみレンダー
ブラウザでのみレンダーされるべきコンポーネント内で、browser が返した値を use に渡します。
Reload をクリックすると、初期 HTML 内のローディングフォールバックを確認できます。ハイドレーション後、React は 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> </> ); }
サーバレンダリング中は、use(browser()) がコンポーネントをサスペンドさせ、React は最も近いサスペンスバウンダリのフォールバックを HTML に含めます。ブラウザでは use(browser()) が undefined を返し、保存済みの下書きが通常どおりレンダーされます。
トラブルシューティング
“Suspense Exception: This is not a real error!” というエラーが表示される
use を try-catch ブロック内で呼び出しています。use はサスペンスと連携するために内部で例外をスローするので、try-catch でラップすることはできません。代わりに、use を呼び出すコンポーネントをエラーバウンダリでラップしてエラーを処理してください。
function Albums({ albumsPromise }) {
try {
// ❌ Don't wrap `use` in try-catch
const albums = use(albumsPromise);
} catch (e) {
return <p>Error</p>;
}
// ...代わりに、コンポーネントをエラーバウンダリでラップします。
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>“A component was suspended by an uncached promise” という警告が表示される
use に渡したプロミスがキャッシュされていないため、React は再レンダーをまたいでそのプロミスを再利用できません。
これは、レンダー内で fetch や async 関数を直接呼び出した場合によく発生します。
function Albums() {
// 🔴 This creates a new Promise on every render
const albums = use(fetch('/albums'));
// ...
}修正するには、同じインスタンスが再利用されるようにプロミスをキャッシュします。
// ✅ fetchData returns the same Promise for the same URL
const albums = use(fetchData('/albums'));詳しくは、クライアントコンポーネント用にプロミスをキャッシュするを参照してください。