Only plain objects の直し方
実際に出るエラー
クラスのインスタンスを Server Component から Client Component へ渡したときに出るエラーです。
何が起きているか
| 渡すもの | 越えられるか | 備考 |
|---|---|---|
| 文字列 / 数値 / 配列 / オブジェクト | できる | 中身も再帰的に見られる |
| Date / Map / Set | できる | JSON とは違い型のまま渡る |
| クラスのインスタンス | できない | このエラーの原因 |
| プロトタイプが null のオブジェクト | できない | 文言にも書かれている |
| 関数 | できない | 別のエラーになる |
Server Component から Client Component へ渡す props は、値だけがネットワークを越えます。
クラスのインスタンスは、値のほかにプロトタイプ(メソッドの置き場)を持っています。値だけを運ぶと、メソッドが消えた別物になってしまう。React はそういう「一部だけ壊れた値」を作らず、境界で止めます。
| 渡すもの | 越えられるか | 備考 |
|---|---|---|
| 文字列・数値・真偽値 | できる | そのまま |
| 配列・プレーンなオブジェクト | できる | 中身も再帰的に見られる |
| Date・Map・Set | できる | JSON とは違い型のまま渡る |
| BigInt・TypedArray | できる | そのまま |
| Server Component の要素 | できる | children として渡す形 |
| 関数 | できない | 別のエラーになる |
| クラスのインスタンス | できない | このエラーの原因 |
| Symbol | できない | Symbol.for で作ったものは渡せる |
| プロトタイプが null のオブジェクト | できない | 文言にも書かれている |
Date や Map は渡せます。JSON にできるかどうかは基準ではありません。基準は「実行できる形を持っているか」です。
原因はほぼこの3つ
1. ORM が返したオブジェクトをそのまま渡している
いちばん多い形です。ORM によっては、行を単なるオブジェクトではなくモデルのインスタンスとして返します。
const user = await db.user.findFirst()
return <Profile user={user} /> // ← user がインスタンスだと止まる
2. 自作のクラスを渡している
Money、DateRange、Result のような値オブジェクトです。
3. ライブラリの戻り値を渡している
Decimal、Big.js、Temporal のポリフィル、独自の Error サブクラスなどが該当します。
最短の確認手順
エラー文には、止まった props の中身が出ます。まずそれを読んでください。それでも分からないときは、渡す直前でこう確かめます。
console.log(Object.getPrototypeOf(value) === Object.prototype)
// false ならプレーンなオブジェクトではない
直し方
const price = new Money(1200) return <View price={price} />
クラスのインスタンスをそのまま渡している。
const price = new Money(1200) return <View yen={price.yen} label={price.format()} />
表示に要る値だけを取り出す。整形もサーバー側で済ませられる。
推奨:必要な値だけを取り出して渡す
const row = await db.user.findFirst()
const user = {
id: row.id,
name: row.name,
joinedAt: row.joinedAt, // Date はそのままでよい
}
return <Profile user={user} />
これには副次的な効果があります。渡した props はすべてブラウザに届くので、必要な列だけに絞ることは、そのまま「余計なデータを送らない」ことになります。パスワードのハッシュや内部フラグを含む行を丸ごと渡してしまう事故も防げます。
境界を引き直す
そもそも Client Component である必要がないなら、'use client' を外すのがいちばん簡単です。境界を越えなければ、インスタンスのまま扱えます。
やってはいけない直し方:JSON で往復させる
<Profile user={JSON.parse(JSON.stringify(row))} />
エラーは消えますが、
Dateが文字列に変わる(受け取る側で壊れる)undefinedのキーが消える- 送りたくない列まで一緒に送ってしまう
ので、直したことになりません。必要な値を明示的に選ぶほうが、短く書けて安全です。
なぜ開発中に気づきにくいのか
このエラーは描画のときに出ます。prerender される構成ならビルド中に失敗するので気づけますが、リクエストごとに描画される構成では、そのページを開くまで出ません。
さらに、データの中身によって出たり出なかったりします。findFirst() が null を返す経路しか通っていないと、開発中は一度も出ないことがあります。
再発防止
- Server Component から Client Component へ渡す形を、明示的に組み立てると決めておく
- ORM の行をそのまま渡さない
- 渡す props の型を、プレーンな形で先に定義しておく
関連するエラー
関数を渡した場合は文言が変わります。
Event handlers cannot be passed to Client Component props.
原因は同じ「境界を越えられないものを渡した」で、種類が違うだけです。
出典
このページの内容は上記の一次情報を基準に書いています。個人ブログや二次情報を根拠にしていません。