Send setter to `props.children`
So I think this is silly, but at the same time I don't find a way out of it, so I am here asking for help.
I am trying to make a
Let's show some code:
As you can see my attempt is to generalize the
Thanks for the help.
I am trying to make a
ModalModal that can accept any childrenchildren and that children will receive the setOpensetOpen fn to open/close the modal independently.Let's show some code:
interface AddGeneralModalProps
extends DialogProps,
VariantProps<typeof contentVariants> {}
const AddGeneralModal = ({ children, size }: AddGeneralModalProps) => {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className={buttonVariants()}>Open Modal</Button>
</DialogTrigger>
<DialogContent className={cn(contentVariants({ size }))}>
<DialogHeader>
<DialogTitle>Modal Title</DialogTitle>
<DialogDescription>
Modal Desc
</DialogDescription>
</DialogHeader>
{children}
</DialogContent>
</Dialog>
);
};
export default AddGeneralModal;
// Usage:
const SpecificForm = () => {
const { mutate } = api.model.post.useMutation({
onSuccess: async () => {
// Here I would like to close the modal
},
});
return (
<Form>
<Button onClick={ () => mutate() }> Submit < /Button>
</Form>
)
}
const MyPage = () => {
return (
<AddGeneralModal>
<SpecificForm />
</AddGeneralModal>
)
}interface AddGeneralModalProps
extends DialogProps,
VariantProps<typeof contentVariants> {}
const AddGeneralModal = ({ children, size }: AddGeneralModalProps) => {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className={buttonVariants()}>Open Modal</Button>
</DialogTrigger>
<DialogContent className={cn(contentVariants({ size }))}>
<DialogHeader>
<DialogTitle>Modal Title</DialogTitle>
<DialogDescription>
Modal Desc
</DialogDescription>
</DialogHeader>
{children}
</DialogContent>
</Dialog>
);
};
export default AddGeneralModal;
// Usage:
const SpecificForm = () => {
const { mutate } = api.model.post.useMutation({
onSuccess: async () => {
// Here I would like to close the modal
},
});
return (
<Form>
<Button onClick={ () => mutate() }> Submit < /Button>
</Form>
)
}
const MyPage = () => {
return (
<AddGeneralModal>
<SpecificForm />
</AddGeneralModal>
)
}As you can see my attempt is to generalize the
DialogDialog component from Shadcn/ui but I really cannot wrap my head around on how to pass setOpensetOpen down to SpecificFormSpecificForm and also make TypeScript happy.Thanks for the help.
Solution
My preferred way though is to hoist the state of the modal outside of the JSX and store it in a hook.
const useModal = () => {
const [open, setOpen] = useState(false);
// any other modal-related state can go in this hook
return { open, setOpen }
};
interface AddGeneralModalProps
extends DialogProps,
VariantProps<typeof contentVariants> {
modal: ReturnType<typeof useModal>
}
const AddGeneralModal = ({ children, size, modal }: AddGeneralModalProps) => {
return (
<Dialog open={modal.open} onOpenChange={modal.setOpen}>
<DialogTrigger asChild>
<Button className={buttonVariants()}>Open Modal</Button>
</DialogTrigger>
<DialogContent className={cn(contentVariants({ size }))}>
<DialogHeader>
<DialogTitle>Modal Title</DialogTitle>
<DialogDescription>
Modal Desc
</DialogDescription>
</DialogHeader>
{children}
</DialogContent>
</Dialog>
);
};
export default AddGeneralModal;
// Usage:
const SpecificForm = (props: { onSuccess: () => void | Promise<void>}) => {
const { mutate } = api.model.post.useMutation({
onSuccess: async () => {
await props.onSuccess()
},
});
return (
<Form>
<Button onClick={ () => mutate() }> Submit < /Button>
</Form>
)
}
const MyPage = () => {
const modal = useModal();
return (
<AddGeneralModal modal={modal}>
<SpecificForm onSuccess={() => modal.setOpen(false)} />
</AddGeneralModal>
)
}const useModal = () => {
const [open, setOpen] = useState(false);
// any other modal-related state can go in this hook
return { open, setOpen }
};
interface AddGeneralModalProps
extends DialogProps,
VariantProps<typeof contentVariants> {
modal: ReturnType<typeof useModal>
}
const AddGeneralModal = ({ children, size, modal }: AddGeneralModalProps) => {
return (
<Dialog open={modal.open} onOpenChange={modal.setOpen}>
<DialogTrigger asChild>
<Button className={buttonVariants()}>Open Modal</Button>
</DialogTrigger>
<DialogContent className={cn(contentVariants({ size }))}>
<DialogHeader>
<DialogTitle>Modal Title</DialogTitle>
<DialogDescription>
Modal Desc
</DialogDescription>
</DialogHeader>
{children}
</DialogContent>
</Dialog>
);
};
export default AddGeneralModal;
// Usage:
const SpecificForm = (props: { onSuccess: () => void | Promise<void>}) => {
const { mutate } = api.model.post.useMutation({
onSuccess: async () => {
await props.onSuccess()
},
});
return (
<Form>
<Button onClick={ () => mutate() }> Submit < /Button>
</Form>
)
}
const MyPage = () => {
const modal = useModal();
return (
<AddGeneralModal modal={modal}>
<SpecificForm onSuccess={() => modal.setOpen(false)} />
</AddGeneralModal>
)
}