「Objects are not valid as a React child (found: object with keys {then})」の修正 — JSXでPromiseをレンダリングしている場合

beginner⚛️ React2026-05-05| React 16+、Next.js、Vite — コンポーネント内でasync/awaitやPromiseを使用するすべてのプロジェクト

Error Message

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.
#react#promise#async#jsx#レンダリング

エラーの内容

ブラウザを開くと、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を使用してください。

Related Error Notes