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()}khigetUserlà async - Biến state được gán bằng Promise thay vì giá trị đã được resolve
- Callback của
.map()có từ khóaasynctrong 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.logtrướ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
useEffecttrong Client Component.

