React19のuseフックの注意点
useフックとは
React19から追加された新機能で、Action(非同期処理)を直接Reactコンポーネント内で扱える新機能です。
- use(promise) のように書くと、Promiseの解決をReactが自動的に待ち、結果をそのまま値として使える。
- Suspenseと組み合わせて、ローディングやエラー処理も簡単にできる。
An unknown Component is an async Client Component.エラー
use()にAction(非同期処理)をそのままセットするとAn unknown Component is an async Client Component.エラーが出ます。
import React, { use } from "react";
const getBooksDataAction = async () => {
const res = await fetch('https://test/books');
return res.json()
}
const BooksFetcher = () => {
// Action(非同期処理)をそのままセット
// An unknown Component is an async Client Component.エラー発生
const books = use(getBooksDataAction())
return <Book books={books}/>
}
どうすればいいか?
原因の前に解決策をお伝えします。以下のようにAction(非同期処理)を関数コンポーネントの外側で変数にセットし、その変数をuse()に渡すようにする。
import React, { use } from "react";
const getBooksDataAction = async () => {
const res = await fetch('https://test/books');
return res.json()
}
// 追加
const getBooksDataPromise = getBooksDataAction();
const BooksFetcher = () => {
// const books = use(getBooksDataAction())
const books = use(getBooksDataPromise) // 追加
return <Book books={books}/>
}
エラーの原因
大前提としてReactに次のルールがあります。
- サーバーコンポーネントならasync関数が使えます。
- クライアントコンポーネントではasync関数は使えません(エラーになります)。
- クライアントコンポーネントから呼ばれるコンポーネントは必ずクライアントコンポーネントになる
- React単体ではサーバーコンポーネントは使えない
最初のエラーが出る書き方(use()にActionを渡す書き方)だと、Reactは「async関数をクライアントコンポーネントで使ってる?」と勘違いします。この勘違いの結果、エラーが発生します。
勘違いの流れ
getBooksDataAction() はasync関数なのでPromiseを返す。
↓
これをuse()に直接渡すと、BooksFetcherコンポーネントは毎回新しいPromiseを生成してしまう。(毎回:ページ遷移/リロード/親コンポーネントの再レンダリングのタイミング)
↓
Reactが「このコンポーネントはasyncかも?」と誤認する。
↓
BooksFetcherコンポーネントはクライアントコンポーネントだから、「クライアントコンポーネントでasyncを使っているやん!」
↓
エラー発生
勘違いを防ぐには...
「BooksFetcherコンポーネントは毎回新しいPromiseを生成してしまう。」の部分をどうにかすれば良い。
use(getBooksDataAction())とすると、BooksFetcherコンポーネントが再レンダリングされる度にgetBooksDataAction()を実行しているのと同じことになる。
なので、use(getBooksDataAction())ではなく、BooksFetcherコンポーネントの外側で定義したconst getBooksDataPromise = getBooksDataAction();をuse()にセットすることでOK。
なぜOKになるかというとBooksFetcherコンポーネントが再レンダリングされる際に評価される範囲は
BooksFetcher = () => {...}の中だけ!
const BooksFetcher = () => {
// ここに記述されている部分が再レンダリングの評価対象!
}
つまりBooksFetcher = () => {...}の外側の部分は、BooksFetcherコンポーネントが初めてimportされたタイミング(=クライアントで初回ロード時)に一度だけ評価される。
Discussion