I have a dropdown component. Which will have 2 props.
Here is my component:
import { useLayer } from "react-laag";
import { ReactElement, useState } from "react";
import classNames from 'classnames';
interface Props {
trigger?: string | ReactElement
children?: ReactElement | Array<ReactElement>
className?: string
}
const Dropdown = ({ trigger, children, className }: Props) => {
const [isOpen, setOpen] = useState(false);
// helper function to close the menu
function close() {
setOpen(false);
}
const { renderLayer, triggerProps, layerProps } = useLayer({
isOpen,
onOutsideClick: close, // close the menu when the user clicks outside
onDisappear: close, // close the menu when the menu gets scrolled out of sight
overflowContainer: true, // keep the menu positioned inside the container
auto: true, // automatically find the best placement
placement: "bottom-end", // we prefer to place the menu "top-end"
triggerOffset: 12, // keep some distance to the trigger
containerOffset: 16, // give the menu some room to breath relative to the container
arrowOffset: 16 // let the arrow have some room to breath also
});
// Again, we're using framer-motion for the transition effect
return (
<div className='dropdown' >
<button className='dropdown-trigger' {...triggerProps} onClick={() => setOpen(!isOpen)}>
{* I want to render trigger here *}
</button>
{isOpen &&
renderLayer(
<div className={classNames(['dropdown-list', className])} {...layerProps}>
{* I want to render list here *}
</div>
)}
</div>
);
}
export default Dropdown
Here I am using react-lagg for the dropdown.
I want to use this component like this:
<Dropdown>
<Dropdown.Trigger>
<div>Some JSX content here</div>
</Dropdown.Trigger>
<Dropdown.List>
<div>Some JSX content here</div>
</Dropdown.List>
</Dropdown>
How can I implement this patter on react?
Basically if you want to use that component that way, try this. **I will write it in JS
const Dropdown = ({ children }) => {
return <div className="dropdown">{children}</div>;
};
const Trigger = (triggerProps) => (
<button className="dropdown-trigger" {...triggerProps}>
{triggerProps.children}
</button>
);
const List = (listProps) => (
<div className="dropdown-list" {...listProps}>
{listProps.children}
</div>
);
Dropdown.Trigger = Trigger;
Dropdown.List = List;
export default Dropdown;
In your case, Dropdown.Trigger is triggering state from Dropdown. I'm not sure it's the best practice, you could write this way
import React, { useState, cloneElement } from 'react';
import classNames from 'classnames';
const Trigger = (triggerProps) => (
<button className="dropdown-trigger" {...triggerProps}>
{triggerProps.children}
</button>
);
const List = ({ className, ...listProps }) => (
<div className={classNames(['dropdown-list', className])} {...listProps}>
{listProps.children}
</div>
);
const Dropdown = ({ trigger, children, className }) => {
const [isOpen, setOpen] = useState(false);
return (
<div className="dropdown">
{cloneElement(
children.find(({ type }) => type === Trigger),
{
onClick: () => setOpen(!isOpen),
}
)}
{isOpen &&
cloneElement(
children.find(({ type }) => type === List),
{
className: className,
}
)}
</div>
);
};
Dropdown.Trigger = Trigger;
Dropdown.List = List;
export default Dropdown;
will be used like this
<Dropdown className="test">
<Dropdown.Trigger>trigger</Dropdown.Trigger>
<Dropdown.List>list</Dropdown.List>
</Dropdown>