react-use-modal is a custom hook that provides a flexible and reusable way to manage modals in React applications.
Use a single line of jsx
tag to insert your Modal component
in page and use useModal
configuration as props. Then go from there: update the same modal with dynamic content with functions provided by the hook.
In its basic implementation useModal
lets you manage multiple modals in page in a reusable way without the need to write complex jsx code with as many Modal components (and relative states) as the number of different modals you want.
- Great flexibiliy: use a single
jsx
tag updated dynamically for all the modals in you page... - ...or decouple part of the logic from the hook and manage it yourself in your page/component
- Small and minified bundle size
- Type safe with TypeScript
To install the package, you can use npm or yarn:
npm install @dinneb/react-use-modal --save
or
yarn add @dinneb/react-use-modal
- Import the
useModal
hook
import { useModal } from "@dinneb/react-use-modal";
- Call the hook to get modal configuration and relative helper functions:
modalConfig
andsetModalConfig
.
const { setModalConfig, modalConfig } = useModal();
- Spread
modalConfig
on your Modal component (you can find an example of a custom Modal component implementation below)
const MyPageComponent = () => {
const { setModalConfig, modalConfig } = useModal();
return (
<>
...Some page content here...
<Modal {...modalConfig} />
</>
);
};
- Use the
setModalConfig
function wherever you want passing a configuration object to it.
const MyPageComponent = () => {
const { setModalConfig, modalConfig } = useModal();
const handleOpenModal = () => {
setModalConfig({
open: true,
title: "I'm the first modal",
children:
"Content message of the modal inside a component of your choice",
buttons: [
{
text: "Confirm",
},
],
});
};
return (
<>
...Some page content here...
<div onClick={handleOpenModal} className="btn">
Show modal
</div>
<Modal {...modalConfig} />
</>
);
};
Basic example: click on "Show modal" button will execute
setModalConfig
with the config we defined.
Maybe you want to update only part of current modalConfig
, let's say we have an open modal and we would like to change part of it's content when the user presses one of it's buttons. We can achieve this by using the functino updateModalConfig
returned by the hook like so
const MyPageComponent = () => {
const { setModalConfig, modalConfig, updateModalConfig } = useModal();
const handleOpenModal = () => {
setModalConfig({
open: true,
title: "I'm the first modal",
children:
"Content message of the modal inside a component of your choice",
buttons: [
{
text: "Cancel",
},
{
text: "Confirm",
disableClose: true,
onClick: showSecondModal,
},
],
});
const showSecondModal = () => {
updateModalConfig({
title: "I'm the second modal",
children:
"Are you sure you want to proceed?",
buttons: [
{
text: "Yes, I am!",
},
],
});
}
// ... same jsx content as seen above
};
Here we've added a second button, the cancel button simply abort the operation closing the modal (so has no need for additional handlers, the confirm button is stopped from closing the modal, we pass it a handler which calls updteModalConfig
from the hook and we update with it only the parts of the config that we need.
Alternative usage: here you can see
updateModalConfig
in action, passing from previous config (left) to partially modified one (right).
If you have some complex, maybe verbose piece of jsx
(eg. for children
or buttons
) you can decouple some props handling from the hook thus leveraging from it only part of the configuration while managing the other part manually in your page/component, let's see an example for state dependent children.
const initialModalConfig = {
title: "I'm a complex modal!",
};
const MyPageComponent = () => {
const { modalConfig, showModal } = useModal(initialModalConfig);
const [isSomethingActive, setIsSomethingActive] = useState < boolean > false;
const currentButtons: IModalButton[] = isSomethingActive
? [{ text: "Button for active state" }]
: [{ text: "Inactive btn 1" }, { text: "Inactive btn 2" }];
return (
<>
<div onClick={showModal} className="btn">
Show modal
</div>
<div
onClick={() => setIsSomethingActive((prev) => !prev)}
className="btn"
>
Toggle active state
</div>
<Modal {...modalConfig} buttons={currentButtons}>
{isSomethingActive ? (
<div>Some jsx here for the active state</div>
) : (
<div>Some other jsx for inactive state</div>
)}
</Modal>
</>
);
};
Here yoy can see an example of useModal
usage with non-empty initial configuration, showing the modal through showModal
function and more importantly the decoupled/rewritten props children
and buttons
. This pattern can be used for complex, verbose or state dependent content.
Alternative usage: config through hook inital config, modal shown with
showModal
, rewrittenbuttons
andchildren
Modal props.
At the moment we don't provide the Modal component
. The hook provides config and functions, you are supposed to adjust your Modal component to them. Soon we will provide an official Modal too. In the meantime you can find below an example of how we implemented the Modal for the examples seen in this doc.
// Modal.tsx
import { ModalProps } from "@dinneb/react-use-modal";
import styles from "./Modal.module.css";
export const Modal = ({
open,
title,
children,
handleClose,
showCloseIcon,
buttons,
}: ModalProps) => {
return open ? (
<>
<div className={styles.modalBody}>
{showCloseIcon && (
<div className={styles.close} onClick={handleClose}></div>
)}
<div className={styles.title}>{title}</div>
<div className={styles.message}>{children}</div>
<ModalActions handleClose={handleClose} buttons={buttons} />
</div>
<div className={styles.overlay} onClick={handleClose}></div>
</>
) : null;
};
// ModalActions.tsx
import { ModalProps } from "@dinneb/react-use-modal";
import styles from "./Modal.module.css";
export const ModalActions = ({
buttons,
handleClose,
}: Pick<ModalProps, "buttons" | "handleClose">) => (
<div className={styles.actions}>
{buttons?.map(({ text, disableClose, disabled, onClick, style }, idx) => {
const handleClick = () => {
!disabled && onClick?.();
if (!disableClose) handleClose();
};
return (
<div
key={idx}
onClick={handleClick}
className={styles.btn}
style={style}
>
{text}
</div>
);
})}
</div>
);
See CSS Code
/* Modal.module.css */
.overlay {
position: absolute;
z-index: 1;
width: 100%;
height: 100vh;
left: 0;
top: 0;
background-color: rgba(0, 0, 0, 0.6);
}
.modalBody {
position: absolute;
min-width: 300px;
max-width: 500px;
min-height: 100px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: white;
border-radius: 10px;
z-index: 2;
box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.5);
padding: 15px;
}
.title {
font-weight: bold;
margin-top: 20px;
font-size: 22px;
}
.message {
margin: 20px;
font-size: 16px;
}
.close:after {
position: absolute;
right: 20px;
top: 10px;
line-height: 1em;
font-size: 40px;
display: block;
content: "\00d7";
cursor: pointer;
}
.actions {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 30px;
margin-bottom: 10px;
}
.btn {
padding: 8px;
border-radius: 6px;
border: 1px solid grey;
text-align: center;
min-width: 80px;
cursor: pointer;
}
IUseModalReturn
Property | Type | Default | Description |
---|---|---|---|
modalConfig |
IExtendedModalConfig |
The current configuration of the modal (usable to as Modal props), auto inject handleClose function | |
setModalConfig |
Dispatch<SetStateAction<IModalConfig>> |
Replace the entire configuration of the modal with the IModalConfig object provided as the argument |
|
updateModalConfig |
(config: Partial<IModalConfig>) => void |
Updates the configuration of the modal with the properties provided in the config object (merge with current state) | |
toggleModal |
() => void |
Toggles the visibility of the modal | |
showModal |
() => void |
Shows the modal | |
hideModal |
() => void |
Hides the modal |
IModalConfig
/ IExtendedModalConfig
The following properties can be passed as configuration options when calling the useModal hook, or as props to your modal component.
Your modal is supposed to have (or extend) the same shape of hook configuration interface: in particular the hook implement IModalConfig
interface while the modal is supposed to extend the already extended IExtendedModalConfig
the automatically injects also and handleClose
function to close the modal when clicking on backdrop or non-disabled buttons.
Property | Type | Default | Description |
---|---|---|---|
open |
boolean |
false |
Whether the modal is currently open |
title? |
string |
undefined |
The title of the modal |
showCloseIcon? |
boolean |
undefined |
Whether to show top right close icon |
children? |
ReactNode |
undefined |
The content to display within the modal |
buttons? |
IModalButton[] |
undefined |
Whether the modal is currently open |
IModalButton
Property | Type | Default | Description |
---|---|---|---|
text |
string |
`` | The text to display on the button |
style? |
CSSProperties |
{} |
Custom styles to apply to the button |
onClick? |
() => void |
undefined |
A function to execute when the button is clicked |
disabled? |
boolean |
undefined |
Whether the button should be disabled |
disableClose? |
boolean |
undefined |
Whether the modal should be kept open after clicking the button |
This is project is built with 💙 on top of:
-
React.js: The library for web and native user interfaces
-
Rollup: The JavaScript module bundler
-
Terser: JavaScript mangler and compressor toolkit for ES6+
-
Babel: A JavaScript compiler
-
Typescript: A strongly typed programming language that builds on JavaScript
Although I am committing the code of this project, life is always a team effort. Here you can find a list of people who contributed to this repo.