📝

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.エラーが出ます。

BooksFetcher.tsx
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()に渡すようにする。

BooksFetcher.tsx
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