Back in 2021, when I started React for the first time in my career. I managed modal components by using conditional rendering.
const Component = () => {
// ...
return (
<div>
{visible && <Modal onOk={handleOk} />}
</div>
);
}
This is based on the idea that a component is rendered when it has to.
In another way, modal components decide whether it should be rendered or not by the logic written inside the modal component.
const Component = () => {
// ...
return (
<div>
<Modal visible={visible} onOk={handleOk} />
</div>
);
}
Here, we don't need to do conditional rendering, but passing a prop to render it.
As I dived into React more, I used a context provider to manage modal components so that I can simply use hooks to render modals.
const Component = () => {
const {showModal} = useModal();
const handleModalOpen = () => {
showModal({
message: 'hello',
onConfirm: () => { alert('clicked'); }
});
}
return (
<div>
{/*...*/}
</div>
);
}
const ModalProvider = () => {
// ...
return (
<ModalContext.Provider value={{
showModal,
}}>
<Modal {...modalState} />
</ModalContext.Provider>
);
}
I even wrote a post about this management, here.
In the side project I recently started, I had to create modal components.
I didn't want to write code from different places, I just wanted to call a function and render a modal component, and an idea came to my mind – render a component on a new root.
In this way, we don't write extra code to render a modal somewhere like in the root or wherever. Modal rendering logic is done inside the component.
The implementation could be different depending on your project.
In my project, I wrote the modal component like this:
import { useCallback, useState } from 'react';
import Text from '../../Text';
import Button from '../../Button';
import { createRoot } from 'react-dom/client';
import Input from '../../Input';
type ConfirmModalProps = {
title: string;
titleColor: 'red';
message: string;
confirmText: string;
confirmationPhrase?: string;
cancelText?: string;
onConfirm: VoidFunction;
onCancel: VoidFunction;
};
type ConfirmParameters = Omit<ConfirmModalProps, 'onConfirm' | 'onCancel'> & {
onConfirm?: VoidFunction;
onCancel?: VoidFunction;
};
const ConfirmModal = ({
title,
titleColor,
message,
confirmText,
confirmationPhrase,
cancelText = 'Cancel',
onConfirm,
onCancel,
}: ConfirmModalProps) => {
const [input, setInput] = useState('');
const confirmButtonDisabled =
confirmationPhrase !== undefined && input !== confirmationPhrase;
return (
<div className="absolute z-50 inset-0 bg-black/50">
<div className="fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] rounded-lg p-6 shadow-lg w-full max-w-[calc(100%-2rem)] sm:max-w-lg bg-gray-900 border border-gray-700 flex flex-col gap-2">
<Text size="lg" color={titleColor} className="font-bold">
{title}
</Text>
<Text>{message}</Text>
{confirmationPhrase ? (
<Input
className="w-full"
placeholder={`${confirmationPhrase}`}
onChange={(e) => setInput(e.target.value)}
/>
) : null}
<div className="flex justify-end gap-2 mt-2">
<Button color="lightGray" onClick={onCancel}>
{cancelText}
</Button>
<Button
color="red"
varient="fill"
onClick={onConfirm}
disabled={confirmButtonDisabled}
>
{confirmText}
</Button>
</div>
</div>
</div>
);
};
export const useConfirmModal = () => {
const confirm = useCallback(
({ onConfirm, onCancel, ...params }: ConfirmParameters) => {
const tempElmt = document.createElement('div');
document.body.append(tempElmt);
const root = createRoot(tempElmt);
const handleConfirm = () => {
tempElmt.remove();
onConfirm?.();
};
const handleCancel = () => {
tempElmt.remove();
onCancel?.();
};
root.render(
<ConfirmModal
{...params}
onConfirm={handleConfirm}
onCancel={handleCancel}
/>
);
},
[]
);
return {
confirm,
};
};
The main logic is here:
export const useConfirmModal = () => {
const confirm = useCallback(
({ onConfirm, onCancel, ...params }: ConfirmParameters) => {
const tempElmt = document.createElement('div');
document.body.append(tempElmt);
const root = createRoot(tempElmt);
// ...
root.render(
<ConfirmModal
{...params}
onConfirm={handleConfirm}
onCancel={handleCancel}
/>
);
},
[]
);
return {
confirm,
};
};
It adds the modal component in the body. There is no other code needed. I don't need to write code somewhere else
What I need to do is just calling the function:
const { confirm } = useConfirmModal();
const handleDeleteClick = () => {
confirm({
title: 'Are you absolutely sure?',
message:
'This action cannot be undone. This will permanently delete your account and remove your data from our servers.',
titleColor: 'red',
confirmationPhrase: 'delete my account',
confirmText: 'Yes, delete my account',
});
};
This modal component itself is independent.
It might not be a good fit on your project though, I wanted to introduce a way to manage modal components in react in this post.
I hope you found it helpful.
Happy Coding!
Top comments (0)