Chuyện gì đã xảy ra
Bạn đang truyền một thứ gì đó làm JSX component — một biến, một prop, thứ gì đó lấy ra từ một map — và TypeScript từ chối compile. Lỗi trông như thế này:
JSX element type 'Component' does not have any construct or call signatures. ts(2604)
Lỗi này thường xuất hiện trong các tình huống render động: lưu component vào biến, nhận nó qua prop, hoặc lấy nó từ bảng tra cứu. Giá trị đó chạy hoàn toàn bình thường lúc runtime — React xử lý mà không phàn nàn gì. TypeScript đơn giản là không xác định được kiểu của nó, nên chặn quá trình build.
Tại sao TypeScript ném lỗi này
Để JSX hợp lệ, TypeScript phải xác nhận phần tử đó là function component — (props) => JSX.Element — hoặc là class component kế thừa React.Component. Khi kiểu là any, object, unknown, hoặc một interface không liên quan, phép kiểm tra đó thất bại. TypeScript không có cách nào biết giá trị đó có thể render được.
Tái hiện lỗi
Ví dụ đơn giản nhất:
// ❌ Bị lỗi
const Component = someMap[key]; // được suy luận là `object` hoặc `any`
return <Component />; // ts(2604)
Một trường hợp tinh vi hơn — kiểu prop khai báo sai:
// ❌ Kiểu prop sai
interface Props {
icon: object; // với TypeScript đây chỉ là object thuần, không phải component
}
const Wrapper = ({ icon: Icon }: Props) => {
return <Icon />; // ts(2604)
};
Cách sửa 1 — Dùng đúng kiểu cho component prop
Khi một prop được thiết kế để nhận component, hãy khai báo kiểu là React.ComponentType hoặc React.ElementType. Không dùng object, không dùng any.
import React from 'react';
interface Props {
icon: React.ComponentType; // ✅ bao gồm cả function và class component
// cần truyền props vào nó? Khai báo rõ ràng:
icon: React.ComponentType<{ size?: number }>;
}
const Wrapper = ({ icon: Icon }: Props) => {
return <Icon />; // ✅
};
Cần hỗ trợ cả thẻ HTML như "div" hay "span"? Dùng React.ElementType thay thế — kiểu này bao gồm cả component lẫn phần tử gốc:
interface Props {
as?: React.ElementType; // 'div', 'span', MyComponent — đều hợp lệ
}
const Box = ({ as: Tag = 'div', children }: React.PropsWithChildren<Props>) => {
return <Tag>{children}</Tag>; // ✅
};
Cách sửa 2 — Khai báo kiểu cho map chứa component
Lấy component từ một map? Hãy khai báo kiểu cho map đó để TypeScript biết mỗi giá trị là một component:
import React from 'react';
const componentMap: Record<string, React.ComponentType> = {
home: HomeIcon,
user: UserIcon,
settings: SettingsIcon,
};
const Component = componentMap[key]; // giờ đã có kiểu React.ComponentType
return <Component />; // ✅
Bị kẹt với một giá trị có kiểu kém từ thư viện bên thứ ba mà bạn không thể sửa? Type assertion là lựa chọn cuối cùng — nhưng hãy biết rằng nó loại bỏ type safety cho biến đó:
const Component = someMap[key] as React.ComponentType;
return <Component />; // ✅ — chỉ dùng khi thực sự cần thiết
Cách sửa 3 — Đổi tên biến sang PascalCase
Cái này hay khiến người ta bất ngờ. JSX xử lý tên biến viết thường như thẻ HTML, không phải component. Dù kiểu có hoàn toàn đúng đi nữa, tên viết thường vẫn gây ra vấn đề:
// ❌ React hiểu đây là thẻ HTML tên "component"
const component = MyComponent;
return <component />;
// ✅ PascalCase báo cho React biết đây là component
const Component = MyComponent;
return <Component />;
Destructuring từ props? Đổi tên ngay tại chỗ khi destructure:
const { icon: Icon } = props;
return <Icon />; // ✅
Cách sửa 4 — Kiểm tra khai báo class component
Đang viết class component? Nó phải kế thừa React.Component. Nếu thiếu, TypeScript sẽ coi nó là class thông thường — không có call signature, không có construct signature, chỉ là một object:
// ❌ Thiếu extends — TypeScript sẽ không coi đây là component
class MyButton {
render() {
return <button>Click</button>;
}
}
// ✅
class MyButton extends React.Component {
render() {
return <button>Click</button>;
}
}
Cách sửa 5 — Kiểm tra tsconfig.json khi toàn bộ JSX bị lỗi
ts(2604) xuất hiện trên mọi phần tử JSX, không chỉ những chỗ dynamic? Đó là dấu hiệu của vấn đề cấu hình chứ không phải kiểu dữ liệu. Kiểm tra trường jsx trong tsconfig.json:
{
"compilerOptions": {
"jsx": "react-jsx", // React 17+ — không cần import thủ công
// "jsx": "react" cho các dự án cũ hơn
"lib": ["dom", "esnext"]
}
}
Xác nhận đã sửa xong
Chạy trực tiếp TypeScript compiler — không có lỗi nào nghĩa là bạn đã ổn:
# Kiểm tra kiểu mà không sinh file output
npx tsc --noEmit
# Dự án Next.js hoặc Vite
npm run build
Trong VS Code, hover vào biến component sau khi sửa. Bạn sẽ thấy React.ComponentType<...>, không phải object hay any. Nếu vẫn thấy kiểu mơ hồ, nghĩa là annotation chưa được áp dụng đúng chỗ.
Tóm tắt nhanh
- Component truyền qua prop → khai báo kiểu là
React.ComponentTypehoặcReact.ElementType - Component lấy từ map → khai báo map là
Record<string, React.ComponentType> - Tên biến viết thường → đổi sang PascalCase trước khi dùng trong JSX
- Class component → xác nhận nó có
extends React.Component - Toàn bộ JSX bị lỗi → kiểm tra trường
jsxtrongtsconfig.json
Bài học rút ra
ts(2604) là TypeScript đang phát hiện một loại bug thực sự: vô tình render thứ không phải component. Cách sửa gần như không bao giờ là vấn đề logic — mà là annotation kiểu bị thiếu hoặc sai, thường nằm ở prop hoặc giá trị trong map.
Có hai điều đáng ghi nhớ. Thứ nhất, khai báo kiểu cho component prop là React.ComponentType, không phải object. Thứ hai, luôn đổi tên prop component khi destructure sang PascalCase trước khi dùng trong JSX. Tên viết thường trong JSX sẽ âm thầm trở thành thẻ HTML — TypeScript sẽ bắt lỗi, nhưng thông báo lỗi không phải lúc nào cũng nói rõ điều đó.

