Thông báo lỗi
Bạn đang nhìn chằm chằm vào một màn hình trống, và bảng điều khiển (console) đang hiện đầy những dòng chữ đỏ. Đó là một trải nghiệm mà hầu như mọi lập trình viên React đều phải trải qua. Lỗi thường có dạng như sau:
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
Trình duyệt thực sự đang muốn nói gì
Về cơ bản, React đang nói với bạn rằng: "Bạn yêu cầu tôi hiển thị một thứ gì đó, nhưng tôi không biết nó là cái gì." Khi bạn viết <MyComponent />, React sẽ đánh giá biến đó. Nếu nó là undefined, công cụ hiển thị (rendering engine) sẽ bị lỗi. React chỉ hiểu các thẻ HTML (như 'div' hoặc 'span') hoặc các hàm và lớp JavaScript thực tế.
Sự cố này hầu như luôn xảy ra do các câu lệnh import và export của bạn không khớp với nhau. Một tệp đang gửi đi một gói dữ liệu, nhưng tệp kia lại không nhận được nó một cách chính xác.
1. Thủ phạm chiếm 90% trường hợp: Default so với Named Imports
Tôi thấy lỗi này xảy ra liên tục trong quá trình tái cấu trúc mã (refactoring) diễn ra nhanh chóng. Nếu component của bạn được xuất ra dưới dạng default, bạn không được sử dụng dấu ngoặc nhọn để nhập nó. Nếu đó là một named export, bạn bắt buộc phải sử dụng chúng.
Kịch bản A: Bẫy Default Export
Nếu bạn sử dụng export default, bạn đang gửi toàn bộ tệp như một đơn vị duy nhất.
Sai:
// MyComponent.js
export default function MyComponent() { return <div>Hello</div>; }
// App.js
import { MyComponent } from './MyComponent'; // Lỗi! MyComponent bị undefined ở đây.
Đúng:
import MyComponent from './MyComponent';
Kịch bản B: Không khớp Named Export
Named exports cho phép bạn xuất nhiều thứ từ một tệp, nhưng chúng yêu cầu sự chính xác.
Sai:
// MyComponent.js
export const MyComponent = () => <div>Hello</div>;
// App.js
import MyComponent from './MyComponent'; // Lỗi! Bạn đang tìm một export default không tồn tại.
Đúng:
import { MyComponent } from './MyComponent';
2. Nỗi lo từ "Barrel File" (index.js)
Sử dụng các tệp index.js để dọn dẹp các đường dẫn nhập (import) thư mục là một mô hình tuyệt vời cho đến khi nó gặp trục trặc. Nếu bạn nhập từ ./components thay vì ./components/Button, tệp index.js của bạn phải được ánh xạ một cách hoàn hảo.
Hãy xem xét cấu trúc này:
components/
├── Button.js (using export default)
└── index.js
Tệp index.js của bạn nên trông như thế này:
export { default as Button } from './Button';
Chỉ cần thiếu một dòng trong tệp này, ứng dụng của bạn sẽ cố gắng hiển thị undefined. Nếu bạn vừa thêm một UI component mới, hãy kiểm tra kỹ xem bạn đã thực sự thêm nó vào barrel file chưa trước khi cố gắng sử dụng nó ở nơi khác.
3. Phụ thuộc vòng (Circular Dependencies): Vòng lặp vô tận
Đây là phiên bản khó nhằn nhất của lỗi này. Nếu Component A nhập Component B, nhưng Component B cũng nhập Component A, một trong hai sẽ bị tải dưới dạng undefined vì hệ thống module chưa hoàn tất việc phân tích vòng lặp.
Nếu bạn nghi ngờ có vòng lặp, hãy chuyển logic dùng chung hoặc các sub-component nhỏ vào một tệp thứ ba, ví dụ như SharedUtils.js. Điều này giúp phá vỡ vòng lặp và cho phép cả hai component nhập các phụ thuộc của chúng một cách an toàn.
4. Phân biệt chữ hoa chữ thường trong môi trường Production
Việc phát triển cục bộ trên macOS thường không phân biệt chữ hoa chữ thường. Bạn có thể nhập ./Mybutton trong khi tệp thực tế là MyButton.js, và nó vẫn hoạt động tốt trên máy tính của bạn. Tuy nhiên, khi bạn triển khai lên môi trường Linux như Vercel hoặc AWS, quá trình build sẽ thất bại hoặc tham chiếu sẽ trả về undefined.
Luôn đặt tên tệp khớp chính xác với mã nguồn. Điều này giúp tiết kiệm hàng giờ gỡ lỗi cho các vấn đề kiểu "nó vẫn chạy tốt trên máy của tôi".
Cách xác minh và sửa lỗi trong 30 giây
Đừng đoán mò. Hãy sử dụng console.log ngay trước khi component của bạn bị lỗi. Đó là cách nhanh nhất để xác nhận những nghi ngờ của bạn.
import { MyComponent } from './MyComponent';
// Kiểm tra phần này trong browser console
console.log("Is MyComponent defined?", MyComponent);
const App = () => {
return <MyComponent />;
};
Nếu nhật ký hiển thị undefined, bạn đã xác nhận được vấn đề nằm ở cầu nối import/export, chứ không phải do cú pháp JSX của bạn.
Lời khuyên chuyên nghiệp để phòng tránh
- Ưu tiên sử dụng Named Exports: Tôi thích dùng
export const MyComponent...vì IDE của bạn sẽ tự động hoàn thành việc nhập (import) chính xác 100% thời gian. - Sử dụng TypeScript: TS sẽ phát hiện ra điều này trước khi bạn nhấn 'Lưu'. Nó sẽ không cho phép nhập một thứ không tồn tại trong tệp nguồn.
- Chú ý các bản cập nhật từ bên thứ ba: Các phiên bản chính của thư viện thường thay đổi cách xuất (export) của chúng. Nếu gần đây bạn đã cập nhật
framer-motionhoặclucide-react, hãy kiểm tra tài liệu của chúng để xem liệu một named export có trở thành default export (hoặc ngược lại) hay không.

