Compound Components là pattern trong React cho phép bạn xây dựng một nhóm component phối hợp với nhau để tạo thành một UI duy nhất, trong đó component cha nắm giữ state và logic, còn các component con chia sẻ state đó một cách ngầm định qua React Context thay vì truyền props qua từng tầng.
Hãy hình dung như thẻ <select> và <option> trong HTML: bạn có thể đặt <option> ở bất kỳ đâu bên trong <select>, thêm tiêu đề hay icon xen giữa, trình duyệt vẫn hiểu chúng thuộc về nhau và đồng bộ trạng thái được chọn. Compound Components mang chính sự linh hoạt đó vào React.
Cơ chế dựa trên composition và Context thay vì prop drilling:
- Component cha (
Select) tạo một Context, lưu state nhưvalue,isOpenvà các hàm nhưonChange,setIsOpen, rồi cung cấp quaProvider. - Các component con (
Select.Toggle,Select.List,Select.Option) dùnguseContextđể đọc và tác động lên state đó. Nhờ vậy bạn có thể đổi thứ tự, bọc thêmdivhay điều kiện render mà không phải sửa props ở mọi tầng trung gian. - Pattern này đảo ngược kiểm soát: người dùng quyết định cấu trúc layout, còn logic vẫn được đóng gói tập trung trong component cha.
// Tạo Context và component cha quản lý state
const SelectContext = createContext(null);
function Select({ value, onChange, children }) {
const [isOpen, setIsOpen] = useState(false);
// ghi nhớ value để tránh re-render thừa
const ctx = useMemo(() => ({ value, onChange, isOpen, setIsOpen }), [value, isOpen, onChange]);
return <SelectContext.Provider value={ctx}>{children}</SelectContext.Provider>;
}
function Option({ value, children }) {
const { value: selectedValue, onChange, setIsOpen } = useContext(SelectContext);
return <div onClick={() => { onChange(value); setIsOpen(false); }}>{children}</div>;
}
Select.Option = Option;Lỗi thường gặp là dùng pattern cho component đơn giản chỉ cần một vài props, làm tăng độ phức tạp không cần thiết. Lỗi khác là không memoize object truyền vào Provider, khiến mọi consumer re-render dù state không đổi. Nếu Option được render ngoài Select do đặt portal sai vị trí, useContext sẽ nhận null và gây crash, nên bạn nên kiểm tra context và throw lỗi có thông báo rõ ràng.
Lưu ý: Dùng Compound Components khi bạn cần API linh hoạt, cho phép người dùng tự sắp xếp layout nhưng vẫn muốn giữ logic tập trung. Nếu chỉ cần tái sử dụng logic không liên quan đến UI, headless hook hoặc render prop sẽ gọn hơn.