エラーの内容
ブラウザを開くと、Reactがこのエラーを表示します:
Objects are not valid as a React child (found: object with keys {then}).
If you meant to render a collection of children, use an array instead.
メッセージ中の {then} が手がかりです。Reactが受け取ったのは文字列でも数値でもJSXでもなく、Promiseオブジェクトです。Promiseには .then() メソッドがあり、それによってReactは渡されたものを特定しました。
なぜこのエラーが起きるのか
コードのどこかで、async関数の呼び出しまたはPromiseがawaitされずにJSXの中に直接入り込んでいます。よくあるミスで、主に以下の3つのパターンで発生します:
JSX内でasync関数を直接呼び出している
// ❌ fetchUserName() はasync関数 — 文字列ではなくPromiseを返す
function UserCard() {
return (
<div>
<p>{fetchUserName()}</p>
</div>
);
}
useEffect内でawaitを忘れている
// ❌ setData に渡されるのは解決済みのJSONではなくPromise自体
const [data, setData] = useState(null);
useEffect(() => {
setData(fetch('/api/data').then(r => r.json())); // awaitが抜けている!
}, []);
return <div>{data}</div>;
.map()でasyncコールバックを使っている
// ❌ .map()内のasyncコールバックはPromiseの配列を返す
const items = ids.map(async (id) => {
const res = await fetch(`/api/item/${id}`);
return <li key={id}>{await res.json()}</li>;
});
return <ul>{items}</ul>;
ステップごとの修正方法
ステップ1:JSXにPromiseが漏れている箇所を見つける
JSX内の {} 式で async 関数を呼び出したりPromiseを返したりしている箇所を調べます。確認すべき3つのポイントは:
getUserがasyncの場合、{getUser()}のようなインライン呼び出し- 解決済みの値ではなくPromiseがセットされているstate変数
- シグネチャに
asyncがある.map()コールバック
ステップ2:非同期ロジックをJSXの外へ移す — useState + useEffect
標準的な修正方法はシンプルです。useEffect 内でデータを取得し、解決済みの値をstateに保存して、そのstate変数をレンダリングします。
import { useState, useEffect } from 'react';
function UserCard({ userId }) {
const [userName, setUserName] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadUser() {
const res = await fetch(`/api/users/${userId}`);
const data = await res.json();
setUserName(data.name); // ✅ Promiseではなく解決済みの文字列
setLoading(false);
}
loadUser();
}, [userId]);
if (loading) return <p>Loading...</p>;
return (
<div>
<p>{userName}</p> {/* ✅ 普通の文字列 */}
</div>
);
}
ステップ3:async .map() をPromise.allで修正する
複数のアイテムのデータを取得する必要がある場合は、Promise.all で先にすべてを解決してから結果をstateに保存します。フェッチ中にレンダリングしようとしないでください。
// ✅ stateに保存する前にすべてのPromiseを解決する
useEffect(() => {
async function loadItems() {
const results = await Promise.all(
ids.map(async (id) => {
const res = await fetch(`/api/item/${id}`);
return res.json();
})
);
setItems(results); // 普通のオブジェクトの配列
}
loadItems();
}, [ids]);
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li> {/* ✅ 解決済みのデータ */}
))}
</ul>
);
ステップ4:.then()チェーンとsetStateを混在させない
よくある落とし穴として、.then() チェーンに対して直接 setData() を呼び出す場合があります。チェーンは新しいPromiseを返すため、そのPromiseがそのままstateに入ってしまいます。
// ❌ setDataにはJSONではなくPromiseが渡される
useEffect(() => {
setData(fetch('/api/data').then(r => r.json()));
}, []);
// ✅ 代わりにuseEffect内でasync関数を定義する
useEffect(() => {
async function load() {
const res = await fetch('/api/data');
const json = await res.json();
setData(json);
}
load();
}, []);
ステップ5(Next.js App Router):ServerコンポーネントとClientコンポーネント
Next.js 13以降はasync Serverコンポーネントをサポートしていますが、そのasyncサポートはClientコンポーネントには適用されません。ファイルの先頭に 'use client' がある場合、コンポーネント関数自体をasyncにすることはできません。データ取得をServerコンポーネントに移すか、ClientコンポーネントではuseEffectを使用してください。
// ✅ Serverコンポーネント — asyncはここで正常に動作する
// app/user/[id]/page.tsx
export default async function UserPage({ params }) {
const res = await fetch(`https://api.example.com/users/${params.id}`);
const user = await res.json();
return <div>{user.name}</div>;
}
// ❌ Clientコンポーネント — asyncなコンポーネント関数は動作しない
'use client';
export default async function UserCard() { // これはNG
const user = await getUser();
return <div>{user.name}</div>;
}
修正の確認
- 保存してリロードします。ブラウザの赤いエラーオーバーレイが消えているはずです。
- React DevToolsを開いてコンポーネントのstateを確認します。
Promise {<pending>}ではなく、文字列または普通のオブジェクトが表示されているはずです。 - returnの前に
console.logを追加して、実際に何を扱っているか確認します:
console.log('data is:', data, typeof data); // ✅ 期待する出力: data is: { name: 'Alice' } object // ❌ NGな出力: data is: Promise { } object
- TypeScriptでは、型付きstateがブラウザに到達する前にコンパイル時にこのエラーを検出します:
```
const [user, setUser] = useState<User | null>(null);
// TSがフラグを立てる: setUser(fetchUser()) — fetchUser()はUserではなくPromise<User>を返す
クイックリファレンス
- 症状:エラーに
object with keys {then}と表示される — これは常にPromiseです。 - 根本原因:awaitされずに非同期の戻り値がJSXまたは
setState()に直接渡された。 - 修正パターン:
useEffect→ 内部にasync関数を定義 →setState(解決済みの値)→ stateをレンダリング。 - TypeScriptのヒント:厳格なstateの型定義(
useState<string | null>)により、このランタイムエラーをコンパイル時のエラーに変換できます。 - Next.jsのヒント:asyncコンポーネントはServerコンポーネント専用です。ClientコンポーネントではuseEffectを使用してください。

