Sửa lỗi 'Objects are not valid as a React child (found: object with keys {then})' — Render Promise trong JSX

beginner⚛️ React2026-05-05| React 16+, Next.js, Vite — mọi dự án sử dụng async/await hoặc Promise bên trong JSX component

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

Lỗi Gặp Phải

Bạn mở trình duyệt và React ném vào mặt bạn thông báo này:

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.

Cái {then} trong thông báo đó là dấu hiệu rõ ràng nhất. React nhận được một đối tượng Promise — không phải chuỗi, không phải số, không phải JSX. Promise có phương thức .then(), và đó chính xác là cách React nhận ra thứ bạn truyền vào.

Tại Sao Lỗi Này Xảy Ra

Ở đâu đó trong code của bạn, một lời gọi hàm async hoặc một Promise đã đi thẳng vào bên trong JSX — mà không được await trước. Đây là lỗi rất dễ mắc. Dưới đây là ba cách phổ biến nhất dẫn đến tình huống này:

Gọi hàm async trực tiếp trong JSX

// ❌ fetchUserName() là async — nó trả về Promise, không phải chuỗi
function UserCard() {
  return (
    <div>
      <p>{fetchUserName()}</p>
    </div>
  );
}

Quên await bên trong useEffect

// ❌ setData nhận vào chính cái Promise, không phải giá trị JSON đã được resolve
const [data, setData] = useState(null);

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

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

Dùng .map() với callback async

// ❌ Các callback async bên trong .map() trả về một mảng các 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>;

Cách Sửa Từng Bước

Bước 1: Tìm chỗ Promise đang rò vào JSX

Quét qua JSX của bạn để tìm bất kỳ biểu thức {} nào gọi hàm async hoặc trả về Promise. Ba chỗ cần kiểm tra:

  • Lời gọi trực tiếp như {getUser()} khi getUser là async
  • Biến state được gán bằng Promise thay vì giá trị đã được resolve
  • Callback của .map() có từ khóa async trong chữ ký hàm

Bước 2: Tách logic async ra khỏi JSX — dùng useState + useEffect

Cách sửa chuẩn khá đơn giản. Fetch dữ liệu bên trong useEffect, lưu giá trị đã được resolve vào state, rồi render biến 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);  // ✅ Chuỗi đã được resolve, không phải Promise
      setLoading(false);
    }

    loadUser();
  }, [userId]);

  if (loading) return <p>Đang tải...</p>;

  return (
    <div>
      <p>{userName}</p>  {/* ✅ Một chuỗi bình thường */}
    </div>
  );
}

Bước 3: Sửa .map() async bằng Promise.all

Cần fetch dữ liệu cho nhiều phần tử? Hãy resolve tất cả trước với Promise.all, rồi lưu kết quả vào state. Đừng cố render trong khi đang fetch.

// ✅ Resolve hết tất cả promise trước khi lưu vào 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);  // Mảng các object bình thường
  }

  loadItems();
}, [ids]);

return (
  <ul>
    {items.map((item) => (
      <li key={item.id}>{item.name}</li>  {/* ✅ Dữ liệu đã được resolve */}
    ))}
  </ul>
);

Bước 4: Không trộn lẫn chuỗi .then() với setState

Một cái bẫy tinh vi: gọi setData() trực tiếp trên một chuỗi .then(). Chuỗi đó trả về một Promise mới — và cái Promise đó đi thẳng vào state của bạn.

// ❌ setData nhận vào Promise, không phải JSON
useEffect(() => {
  setData(fetch('/api/data').then(r => r.json()));
}, []);

// ✅ Định nghĩa một hàm async bên trong useEffect thay thế
useEffect(() => {
  async function load() {
    const res = await fetch('/api/data');
    const json = await res.json();
    setData(json);
  }
  load();
}, []);

Bước 5 (Next.js App Router): Server Component và Client Component

Next.js 13+ hỗ trợ Server Component async — nhưng hỗ trợ async đó không áp dụng cho Client Component. Nếu file của bạn có 'use client' ở đầu, bạn không thể khai báo hàm component là async. Hãy chuyển việc fetch dữ liệu sang Server Component, hoặc dùng useEffect bên trong Client Component.

// ✅ Server Component — async hoạt động tốt ở đây
// 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 — hàm component async sẽ gây lỗi
'use client';
export default async function UserCard() {  // Đừng làm vậy
  const user = await getUser();
  return <div>{user.name}</div>;
}

Kiểm Tra Sau Khi Sửa

  • Lưu file và tải lại trang. Lớp phủ lỗi màu đỏ trong trình duyệt phải biến mất.
  • Mở React DevTools và kiểm tra state của component — bạn sẽ thấy chuỗi hoặc object bình thường, không phải Promise {<pending>}.
  • Thêm console.log trước lệnh return để xác nhận bạn đang làm việc với thứ gì:

console.log('data is:', data, typeof data); // ✅ Nên in ra: data is: { name: 'Alice' } object // ❌ Không phải: data is: Promise { } object

  
  - Trong TypeScript, state được định kiểu sẽ bắt lỗi này ngay lúc biên dịch — trước khi nó chạm tới trình duyệt:
    ```
const [user, setUser] = useState<User | null>(null);
// TS sẽ báo lỗi: setUser(fetchUser()) — fetchUser() trả về Promise<User>, không phải User

Tóm Tắt Nhanh

  • Triệu chứng: Lỗi báo object with keys {then} — đó luôn luôn là một Promise.
  • Nguyên nhân gốc rễ: Giá trị trả về từ async đi thẳng vào JSX hoặc setState() mà không được await.
  • Cách sửa: useEffect → định nghĩa hàm async bên trong → setState(resolvedValue) → render state.
  • Mẹo TypeScript: Định kiểu state chặt chẽ (useState<string | null>) biến lỗi runtime này thành lỗi compile-time.
  • Mẹo Next.js: Component async chỉ dành cho Server Component. Dùng useEffect trong Client Component.

Related Error Notes