Fix 'Objects are not valid as a React child (found: object with keys {then})' โ€” Rendering a Promise in JSX

beginnerโš›๏ธ React2026-05-05| React 16+, Next.js, Vite โ€” any project using async/await or Promises inside JSX components

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#render

The Error

You open the browser and React slaps you with this:

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.

That {then} in the message is the giveaway. React received a Promise object โ€” not a string, not a number, not JSX. Promises have a .then() method, and that's exactly how React identified what you passed it.

Why This Happens

Somewhere in your code, an async function call or a Promise ended up directly inside JSX โ€” without being awaited first. It's an easy mistake. Here are the three most common ways it happens:

Calling an async function directly in JSX

// โŒ fetchUserName() is async โ€” it returns a Promise, not a string
function UserCard() {
  return (
    <div>
      <p>{fetchUserName()}</p>
    </div>
  );
}

Forgetting to await inside useEffect

// โŒ setData receives the Promise itself, not the resolved JSON
const [data, setData] = useState(null);

useEffect(() => {
  setData(fetch('/api/data').then(r => r.json())); // Missing await!
}, []);

return <div>{data}</div>;

Using .map() with an async callback

// โŒ async callbacks inside .map() return an array of Promises
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>;

Step-by-Step Fix

Step 1: Find where the Promise is leaking into JSX

Scan your JSX for any {} expression that calls an async function or returns a Promise. Three places to check:

  • Inline calls like {getUser()} where getUser is async
  • State variables set to a Promise rather than a resolved value
  • .map() callbacks with async in the signature

Step 2: Move async logic out of JSX โ€” useState + useEffect

The standard fix is straightforward. Fetch data inside useEffect, store the resolved value in state, then render that state variable.

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);  // โœ… Resolved string, not a Promise
      setLoading(false);
    }

    loadUser();
  }, [userId]);

  if (loading) return <p>Loading...</p>;

  return (
    <div>
      <p>{userName}</p>  {/* โœ… A plain string */}
    </div>
  );
}

Step 3: Fix async .map() with Promise.all

Need to fetch data for multiple items? Resolve everything first with Promise.all, then store the results in state. Don't try to render mid-fetch.

// โœ… Resolve all promises before storing in state
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);  // Array of plain objects
  }

  loadItems();
}, [ids]);

return (
  <ul>
    {items.map((item) => (
      <li key={item.id}>{item.name}</li>  {/* โœ… Resolved data */}
    ))}
  </ul>
);

Step 4: Don't mix .then() chains with setState

A subtle trap: calling setData() directly on a .then() chain. The chain returns a new Promise โ€” and that Promise goes straight into your state.

// โŒ setData receives a Promise, not the JSON
useEffect(() => {
  setData(fetch('/api/data').then(r => r.json()));
}, []);

// โœ… Define an async function inside useEffect instead
useEffect(() => {
  async function load() {
    const res = await fetch('/api/data');
    const json = await res.json();
    setData(json);
  }
  load();
}, []);

Step 5 (Next.js App Router): Server vs. Client Components

Next.js 13+ supports async Server Components โ€” but that async support does not extend to Client Components. If your file has 'use client' at the top, you cannot make the component function itself async. Move data fetching to a Server Component, or use useEffect inside the Client Component.

// โœ… Server Component โ€” async works fine here
// 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 Component โ€” async component function will break
'use client';
export default async function UserCard() {  // Don't do this
  const user = await getUser();
  return <div>{user.name}</div>;
}

Verify the Fix

  • Save and reload. The red error overlay in the browser should be gone.
  • Open React DevTools and inspect your component's state โ€” you should see a string or plain object, not Promise {<pending>}.
  • Drop a console.log before the return to confirm what you're actually working with:

console.log('data is:', data, typeof data); // โœ… Should print: data is: { name: 'Alice' } object // โŒ Not: data is: Promise { } object

  
  - In TypeScript, typed state catches this at compile time โ€” before it ever reaches the browser:
    ```
const [user, setUser] = useState<User | null>(null);
// TS will flag: setUser(fetchUser()) โ€” fetchUser() returns Promise<User>, not User

Quick Reference

  • Symptom: Error says object with keys {then} โ€” that's always a Promise.
  • Root cause: An async return value went directly into JSX or setState() without being awaited.
  • Fix pattern: useEffect โ†’ async function inside โ†’ setState(resolvedValue) โ†’ render state.
  • TypeScript tip: Strict state types (useState<string | null>) turn this runtime crash into a compile-time error.
  • Next.js tip: Async components are Server Component-only. Use useEffect in Client Components.

Related Error Notes