Event handlers cannot be passed の直し方
実際に出るエラー
Server Component から Client Component へ関数を props で渡したときに出るエラーです。
何が起きているか
越えられる: items(配列)
越えられる: title(文字列)
越えられない: onAdd(関数)
App Router のコンポーネントは、既定で Server Component です。Server Component が返した内容は、そのままブラウザに届くわけではありません。いったん値だけの形に変換されてネットワークを越え、ブラウザ側で組み立て直されます。
関数はこの変換ができません。関数の中身は「実行できる形」であって、値ではないからです。だから境界で止められます。
越えられる: 文字列・数値・配列・オブジェクト
越えられる: Date・Map・Set・BigInt
越えられない: 関数(このエラーの原因)
越えられない: クラスのインスタンス
原因はほぼこの3つ
1. onClick や onChange を Server Component から渡している
いちばん多い形です。
// app/cart/page.tsx(Server Component)
export default async function Page() {
const items = await db.item.findMany()
return <List items={items} onAdd={add} /> // ← ここ
}
2. コールバックを props 名で渡している
onXxx という名前でなくても、関数なら越えられません。ただし文言が変わります。formatter={fn} や render={fn} のように on で始まらない名前だと、Functions cannot be passed directly to Client Components と出ます(Functions cannot be passed directly の直し方)。
3. ライブラリのコンポーネントに関数を渡している
自分では境界を意識していなくても、そのライブラリが Client Component であれば同じことが起きます。
最短の確認手順
- エラー文の
^^^^^^^^^^が指している props 名を見る - その props を渡している側のファイルを開く
- そのファイルの先頭に
'use client'があるか確認する - 無ければ、そこが Server Component です。渡している値が関数なら、それが原因です
直し方
// page.tsx(Server Component) export default function Page() { return <List onAdd={() => add()} /> }
関数を渡そうとしている。ビルドで落ちる。
// List.tsx 'use client' export function List({ items }) { return <button onClick={() => add()}>追加</button> }
動作は List の中に書く。渡すのは値だけ。
推奨:ハンドラを Client Component の内側で定義する
境界を「ブラウザの操作が必要な部品」まで下ろします。
// app/cart/page.tsx(Server Component のまま)
export default async function Page() {
const items = await db.item.findMany()
return <List items={items} />
}
// components/List.tsx
'use client'
export function List({ items }: { items: Item[] }) {
const add = (id: number) => { /* ブラウザ側の処理 */ }
return items.map((i) => <button key={i.id} onClick={() => add(i.id)}>追加</button>)
}
Page は Server Component のままなので、DB に直接アクセスできますし、商品データを扱うコードはブラウザに送られません。
サーバー側で処理したい場合:Server Function を渡す
「押したらサーバーで何かする」なら、Server Function は props として渡せます。関数そのものではなく参照だけが渡り、呼ぶとサーバー側で実行されるためです。
// app/cart/actions.ts
'use server'
export async function addToCart(id: number) {
// サーバー側で実行される
}
やってはいけない直し方:Page に use client を付ける
エラーは消えます。境界を越えなくなるからです。ですが、
- 商品データを取る処理まで含めてブラウザ側に置かれる
- DB への直接アクセスが成立しなくなる
- ページ全体の JavaScript がブラウザへ送られる
ので、解決にはなっていません。エラーが消えることと、正しく直ったことは違います。
なぜ開発中に気づきにくいのか
このエラーは描画のときに出ます。そのページが静的に prerender される構成なら、その描画はビルド中に走るので、ビルドが失敗して気づけます。
一方、リクエストごとに描画される構成だと、そのページを実際に開くまで出ません。開発中に触っていない画面だと、本番で初めて表面化することがあります。
再発防止
- 境界は葉に近い側へ引く。ページ全体に
use clientを付けない - Server Component から渡す props は「値」だけ、と決めておく
- ライブラリのコンポーネントを使うときは、それが Client Component かどうかを先に確認する
関連するエラー
クラスのインスタンスを渡した場合は、別の文言になります。
Only plain objects, and a few built-ins, can be passed to Client Components
from Server Components. Classes or null prototypes are not supported.
原因は同じ「境界を越えられないものを渡した」で、越えられないものの種類が違うだけです。
出典
このページの内容は上記の一次情報を基準に書いています。個人ブログや二次情報を根拠にしていません。