I am trying to create a collapsable container component that could be reused for different parts of a form in order to show the fields or a summary of that section. In my case, each container would contain a Form object passed as a child to that container. The base of my code goes as follows :
function CollapsableContainer({ title, children }: CollapsableContainerProps) {
const [isOpen, setIsOpen] = useState(false)
return (
<div className="comp-collapsable-container">
{isOpen ? (
{children}
) : (
<>
<div className="header-section">
<Typography variant="h6">
{title}
</Typography>
<Button onClick={() => setIsOpen(true)} startIcon={<EditIcon />}>
Modify
</Button>
</div>
<div className="summary-section">
/* Summary here */
</div>
</>
)}
</div>
)
}
I am wondering if it's possible to pass setIsOpen to the children in order to allow the container to collapse once the user pressed a button that that child would contain instead of having to replicate that behaviour multiple times. (Basically without copy/pasting my code)
To give more context, a child would look something like this, where the setIsOpen would be added to the onSubmit function :
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
>
<Form>
<FormikTextField
id="name"
name="name"
label="name"
fullWidth
required
/>
<FormikTextField
id="description"
name="description"
label="description"
fullWidth
required
/>
</Form>
</Formik>
Thank you for any help or suggestion!
CodePudding user response:
I was able to create a CodeSandbox to you.
You have to type your CollapsableContainerProps as follows. You are stating that children will require props that extend the interface RequiresSetIsOpenProps. This is from this answer.
interface RequiresSetIsOpenProps {
setIsOpen?: Dispatch<SetStateAction<boolean>>;
}
interface CollapsableContainerProps {
title: string;
children:
| React.ReactElement<RequiresSetIsOpenProps>
| React.ReactElement<RequiresSetIsOpenProps>[];
}
Then, you can create any sort of child components you want that satisfy this condition:
interface SomeButtonProps {
// add your other child props here.
title: string;
}
function SomeButton(props: RequiresSetIsOpenProps & SomeButtonProps) {
const { setIsOpen, title } = props;
function handleClick() {
if (setIsOpen !== undefined) setIsOpen(false);
}
return <button onClick={handleClick}>{title}</button>;
}
Finally, inside your CollapsableContainer you have to apply the setIsOpen function to each of the children. I got this from another answer. You are telling React to copy whatever children were passed in and slap the setIsOpen function on top of the existing props.
function CollapsableContainer({ title, children }: CollapsableContainerProps) {
const [isOpen, setIsOpen] = useState(true);
return (
<div className="comp-collapsable-container">
{isOpen ? (
React.Children.map(children, (child) => {
return React.cloneElement(child, {
setIsOpen: setIsOpen
});
})
) : (
<>
<div className="header-section">
<h6>{title}</h6>
<button onClick={() => setIsOpen(true)}>Modify</button>
</div>
<div className="summary-section">/* Summary here */</div>
</>
)}
</div>
);
}
Here is the CodeSandbox to check it out! I hope this helps!