I have a Button
component that allows some props like variant
, active
, size
etc. to change its styling.
What I want is, passing the Button component to another component and use it there as a normal component.
function Header() {
return (
<div>
<Button.Link href="/">Log in</Button.Link>
<Button.Link href="/">Sign up</Button.Link>
<Dropdown
Trigger={
<Button variant="outline" size="icon">
<MenuIcon className="size-4" />
</Button>
}
>
<div className="absolute right-0 top-8 h-72 w-64 bg-red-200">Hello</div>
</Dropdown>
</div>
);
}
export default Header;
As you can see I am passing the Button
component to the Dropdown
component.
Now in Dropdown, I want to use it as shown below: (dummy code for explanation)
interface DropdownProps {
Trigger: any;
children: React.ReactNode;
}
function Dropdown({ Trigger, children }: DropdownProps) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const toggleDropdown = () => {
setIsOpen((current) => !current);
};
useClickOutside(dropdownRef, () => setIsOpen(false));
return (
<div ref={dropdownRef} className="relative">
{<Trigger onClick={toggleDropdown} active={isOpen} />} <-- like this
{isOpen && children}
</div>
);
}
export default Dropdown;
How can I achieve this functionality?
What I have done:
I found a solution using React’s cloneElement
but not happy with it (feels like there is a better way).
interface DropdownProps {
trigger: React.ReactElement<{
onClick: () => void;
active: boolean;
}>;
children: React.ReactNode;
}
function Dropdown({ trigger, children }: DropdownProps) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const toggleDropdown = () => {
setIsOpen((current) => !current);
};
useClickOutside(dropdownRef, () => setIsOpen(false));
return (
<div ref={dropdownRef} className="relative">
{cloneElement(trigger, { onClick: toggleDropdown, active: isOpen })}
{isOpen && children}
</div>
);
}
export default Dropdown;
Anything better?