Màn hình đỏ: React.Children.onlyCó lẽ bạn đang nhìn chằm chằm vào một màn hình thông báo lỗi màu đỏ rực. Đây là một sự ức chế thường gặp khi xây dựng các thư viện UI hoặc các wrapper tùy chỉnh. Thông báo này rất thẳng thừng:
Error: React.Children.only expected to receive a single React element child.
Điều này xảy ra khi một component—có thể là Tooltip, Portal, hoặc Context Provider—mong đợi chính xác một component con nhưng lại nhận được thứ khác. Ở đâu đó trong mã nguồn, component gọi React.Children.only(children). Nếu prop children đó không chứa phần tử nào hoặc có từ hai phần tử trở lên, React sẽ ném ra một ngoại lệ ngay lập tức.
Tại sao React lại nghiêm ngặt như vậyTiện ích React.Children.only() đóng vai trò như một người gác cổng. Nó đảm bảo component của bạn không bị lỗi khi cố gắng thực hiện các thao tác chỉ có ý nghĩa đối với một nút duy nhất, chẳng hạn như chèn một ref cụ thể hoặc áp dụng các kiểu CSS riêng biệt. Ngay cả một chuỗi văn bản đơn giản đặt cạnh một nút bấm cũng sẽ kích hoạt lỗi này. React coi đó là hai component con riêng biệt: một nút văn bản và một phần tử.
Kịch bản: Một Tooltip bị lỗiHãy tưởng tượng bạn có một wrapper Tooltip tùy chỉnh. Nó được thiết kế để bao bọc một nút bấm duy nhất và hiển thị nhãn khi di chuột qua. Đây là logic gây ra lỗi:
const Tooltip = ({ children, text }) => {
// Dòng này là điểm gây lỗi duy nhất
const child = React.Children.only(children);
return (
<div className="tooltip-container">
{child}
<span className="tooltip-text">{text}</span>
</div>
);
};
Cách này hoạt động hoàn hảo cho một phần tử duy nhất:
<Tooltip text="Lưu">
<button>Gửi</button>
</Tooltip>
Tuy nhiên, nó sẽ bị lỗi ngay khi bạn thêm một icon hoặc một đoạn văn bản thứ hai:
<Tooltip text="Lưu">
<span>💾</span>
<button>Gửi</button>
</Tooltip>
Cách khắc phục### 1. Cách sửa nhanh: Bao bọc trong một ContainerGiải pháp nhanh nhất là gộp các component con của bạn vào một thẻ <div> hoặc <span> duy nhất. Điều này đáp ứng yêu cầu về một phần tử cha duy nhất. Bạn chỉ mất khoảng 10 giây để thực hiện.
<Tooltip text="Lưu">
<div>
<span>💾</span>
<button>Gửi</button>
</div>
</Tooltip>
Mẹo chuyên nghiệp: Hãy cẩn thận với cách tiếp cận này. Việc thêm một thẻ <div> bổ sung đôi khi có thể làm hỏng bố cục Flexbox hoặc Grid nếu component cha mong đợi các component con trực tiếp cho các quy tắc CSS của nó.
2. Cách linh hoạt: React.Children.mapNếu bạn là người viết wrapper component, bạn có thể làm cho nó linh hoạt hơn. Thay vì yêu cầu một con duy nhất, hãy lặp qua các con. Điều này cho phép component của bạn xử lý một, hai hoặc mười con một cách mượt mà.
const Tooltip = ({ children, text }) => {
return (
<div className="tooltip-container">
{React.Children.map(children, (child) => {
// Kiểm tra xem đó có phải là một element hợp lệ trước khi cố gắng clone nó
return React.isValidElement(child) ? React.cloneElement(child) : child;
})}
<span className="tooltip-text">{text}</span>
</div>
);
};
3. Bẫy FragmentBạn có thể thử khắc phục điều này bằng cách sử dụng Fragment (<>...</>). Mặc dù về mặt kỹ thuật Fragment được tính là một con duy nhất, nhưng nó có thể gây ra các vấn đề phụ. Nếu component của bạn sử dụng React.cloneElement để truyền prop xuống, các prop đó sẽ được gắn vào Fragment chứ không phải các phần tử bên trong nó. Vì Fragment không tồn tại trong DOM, các style hoặc event listener của bạn sẽ biến mất.
Xác minh và Kiểm thử
- Kiểm tra Console: Tải lại trình duyệt của bạn. Lỗi đỏ sẽ được thay thế bằng giao diện người dùng đã render.
- Kiểm tra DOM: Sử dụng DevTools (F12) để xem liệu thẻ
<div>bổ sung đó (từ Lựa chọn 1) có làm thay đổi khoảng cách bố cục của bạn hay không. - Thử các trường hợp biên: Truyền một chuỗi văn bản thuần túy hoặc giá trị null vào component. Một wrapper thực sự mạnh mẽ không nên bị lỗi chỉ vì
childrentrống.
Lập trình phòng thủ tốt hơn
- Định nghĩa PropTypes: Sử dụng
children: PropTypes.element.isRequired. Điều này cung cấp một cảnh báo rõ ràng trong console trong quá trình phát triển trước khi ứng dụng thực sự bị lỗi. - Xác thực: Luôn bao bọc các lời gọi
cloneElementbên trong một kiểm traReact.isValidElement(). Điều này ngăn chặn lỗi khi người dùng truyền vào chuỗi hoặc số. - Tránh dùng 'only' khi có thể: Trừ khi logic của bạn thực sự yêu cầu một nút DOM duy nhất để tính toán vị trí, hãy để các component của bạn linh hoạt hơn.

