This is a simple modal hook for React.
It is a custom hook that allows you to create a modal with a simple API.
- Installation
- Usage
- Types
# npm
npm i @vegatality/react-hook-modal
# yarn
yarn add @vegatality/react-hook-modal
# pnpm
pnpm add @vegatality/react-hook-modal
View
import { useModalList, type ModalComponent } from '@vegatality/react-hook-modal';
// const SomeModal = ({ closeModal, modalRef }: ModalComponentProps<{ name: string }>) => {
const SomeModal: ModalComponent<{ name: string }> = ({ closeModal, modalRef, name }) => {
return (
<div ref={modalRef}>
<h1>Some Modal</h1>
<p>{name}</p>
<button onClick={closeModal}>Close</button>
</div>
);
};
const App = () => {
const { ModalComponentList, openModal, closeModal, watch, destroy, ... } = useModalList();
return (
<div>
<ModalComponentList />
<button onClick={() => openModal({ modalKey: ['some'], ModalComponent: SomeModal, modalProps: { name: 'some' }, options: { resistBackgroundClick: true } })}>
Open Modal 1
</button>
</div>
);
};
View
import { useModalList, useModalContext, ModalProvider, type ModalComponent } from '@vegatality/react-hook-modal';
const SomeModal: ModalComponent<{ name: string }> = ({ closeModal, modalRef, name }) => {
return (
<div ref={modalRef}>
<h1>Some Modal</h1>
<p>{name}</p>
<button onClick={closeModal}>Close</button>
</div>
);
};
const ChildComponent = () => {
const { openModal } = useModalContext();
return (
<button onClick={() => openModal({ modalKey: ['some'], ModalComponent: SomeModal, modalProps: { name: 'some' } })}>
Open Modal 1
</button>
);
};
const App = () => {
const methods = useModalList();
const { ModalComponentList, openModal } = methods;
return (
<div>
<ModalComponentList />
<ModalProvider {...methods}>
<ChildComponent />
</ModalProvider>
</div>
);
};
View
import {
useGlobalModalList,
GlobalModalList,
GlobalModalListProvider,
type ModalComponentProps,
} from '@vegatality/react-hook-modal';
const SomeModal = ({ closeModal, modalRef, name }: ModalComponentProps<{ name: string }>) => {
return (
<div ref={modalRef}>
<h1>Some Modal</h1>
<p>{name}</p>
<button onClick={closeModal}>Close</button>
</div>
);
};
const App = () => {
const { openGlobalModal } = useGlobalModalList(); // useGlobalModalList hook uses useContext hook under the hood
return (
<button
onClick={() => openGlobalModal({ modalKey: ['some'], ModalComponent: SomeModal, modalProps: { name: 'some' } })}
>
Open Modal 1
</button>
);
};
const Main = () => {
return (
<GlobalModalListProvider>
<App />
<GlobalModalList />
</GlobalModalListProvider>
);
};
You can manage the modal key hierarchically.
If you pass an array as a modal key, it will be managed hierarchically.
const openModal1 = () => {
openModal({
modalKey: ['test'],
ModalComponent: TestModal,
options: { resistBackgroundClick: true },
});
};
const openModal2 = () => {
openModal({
modalKey: ['test', 2],
ModalComponent: TestModal,
options: { resistBackgroundClick: [['test']] }, // This will resist background click until ['test'] modal is closed
});
};
const openGlobalModal1 = () => {
openGlobalModal({
modalKey: ['test', 2, { id: 1 }],
ModalComponent: TestModal,
options: { resistBackgroundClick: false },
});
};
You can open multiple modals with openModal
api from useModalList
hook.
View
import { useModalList, type ModalComponent } from '@vegatality/react-hook-modal';
const SomeModal: ModalComponent<{ name: string }> = ({ closeModal, modalRef, name }) => {
return (
<div ref={modalRef}>
<h1>Some Modal</h1>
<p>{name}</p>
<button onClick={closeModal}>Close</button>
</div>
);
};
const SomeModal2: ModalComponent<{ name: string }> = ({ closeModal, modalRef, name }) => {
return (
<div ref={modalRef}>
<h1>Some Modal2</h1>
<p>{name}</p>
<button onClick={closeModal}>Close</button>
</div>
);
};
const App = () => {
const { ModalComponentList, openModal, closeModal, watch, destroy, ... } = useModalList();
return (
<div>
<ModalComponentList />
<button onClick={() => openModal({ modalKey: ['some'], ModalComponent: SomeModal, modalProps: { name: 'some' }, options: { resistBackgroundClick: true } })}>
Open Modal 1
</button>
<button onClick={() => openModal({ modalKey: ['some2'], ModalComponent: SomeModal2, modalProps: { name: 'some2' }, options: { resistBackgroundClick: false, scrollable: false } })}>
Open Modal 2
</button>
</div>
);
};
You can close the modal using the modal key that was passed when it was opened.
View
const { ModalComponentList, openModal, closeModal, watch, destroy, ... } = useModalList();
closeModal({ modalKey: ['some'] });
If you want to close all modals, you can use the destroy
method.
View
useEffect(
() => () => {
destroy();
},
[],
);
If multiple modals are open, they will close in the order they were opened, starting with the most recently opened modal.
If the option value of the most recently opened modal is true
or if there is a specified modalKey
to resist, it will find and close the most recent modal with the specified option value set to false that was opened before it.
For example, if the resistBackgroundClick
option value of the most recently opened modal is true
or if the modal with the specified modalKey
is still open, it will find and close the earlier opened modal with the resistBackgroundClick
value set to false
when the background is clicked.
You can prevent the modal from closing if the specified modal is open.
Just specify the modalKey of the modal you want to resist in the options
object when opening the modal.
View
import { useModalList, type ModalComponent } from '@vegatality/react-hook-modal';
const SomeModal: ModalComponent<{ name: string }> = ({ closeModal, modalRef, name }) => {
return (
<div ref={modalRef}>
<h1>Some Modal</h1>
<p>{name}</p>
<button onClick={closeModal}>Close</button>
</div>
);
};
const SomeModal2: ModalComponent<{ name: string }> = ({ closeModal, modalRef, name }) => {
return (
<div ref={modalRef}>
<h1>Some Modal2</h1>
<p>{name}</p>
<button onClick={closeModal}>Close</button>
</div>
);
};
const App = () => {
const { ModalComponentList, openModal, closeModal, watch, destroy, ... } = useModalList();
return (
<div>
<ModalComponentList />
<button onClick={() => openModal({ modalKey: ['some', 1], ModalComponent: SomeModal, modalProps: { name: 'some' }, options: { resistBackgroundClick: true } })}>
Open Modal 1
</button>
<button onClick={() => openModal({ modalKey: ['some2'], ModalComponent: SomeModal2, modalProps: { name: 'some2' }, options: { resistBackgroundClick: [['some', 1]] } })}> /* 👈 this will resist background click until ['some', 1] modal is closed */
Open Modal 2
</button>
</div>
);
};
You can set the default options for all modals.
These default options are applied to all modals that are opened without specifying their own options.
However, the options specified when opening each modal will override the default options.
View
import { useModalList, type ModalComponent } from '@vegatality/react-hook-modal';
const SomeModal: ModalComponent<{ name: string }> = ({ closeModal, modalRef, name }) => {
return (
<div ref={modalRef}>
<h1>Some Modal</h1>
<p>{name}</p>
<button onClick={closeModal}>Close</button>
</div>
);
};
const App = () => {
const { ModalComponentList, openModal, closeModal, watch, destroy, ... } = useModalList({
mode: {
resistBackgroundClick: true, // set default option
}
});
return (
<div>
<ModalComponentList />
<button onClick={() => openModal({ modalKey: ['some'], ModalComponent: SomeModal, modalProps: { name: 'some' }, options: { resistBackgroundClick: false } })}> /* 👈 this will override default option */
Open Modal 1
</button>
</div>
);
};
You can close the modal with a hierarchical modal key.
If you want to close a modal with an exactly matched modal key, you must set the exact
option to true
.
const killTestModals = () => {
closeModal({ modalKey: ['test'] }); // This will close modals with key like ['test'], ['test', 1], ['test', 2], ['test', 2, { once: true } ], and etc.
};
const killTestModalExactly = () => {
closeModal({ modalKey: ['test'], exact: true }); // This will only close modal with key ['test']
};
modalRef
is a reference to the modal element.
You have to pass the modalRef
to the element ref.
It determine the valid area of a modal.
View
import { ModalComponent } from '@vegatality/react-hook-modal';
const SomeModal: ModalComponent<{ name: string }> = ({ closeModal, modalRef, name }) => {
return (
<div className='modal_boundary' ref={modalRef}>
<h1>Some Modal</h1>
<p>{name}</p>
<button onClick={closeModal}>Close</button>
</div>
);
};
If you don't pass the modalRef
, the modal will not close even if you set resistBackgroundClick
to false (default).
This is because, without modalRef
, we can't determine the valid boundary of the modal.
However, the modal will close properly when the ESC
key is pressed, regardless of whether the modalRef
is passed.
You can use the useToggleModal
hook to toggle the modal instead of using useModalList
hook.
It is a simple hook that returns the isModalOpen
, modalRef
, and toggleModal
function that only controls a single modal.
View
import { useToggleModal } from '@vegatality/react-hook-modal';
function App() {
const { isModalOpen, modalRef, toggleModal } = useToggleModal({
initialValue: false,
openModalOptions: { resistBackgroundClick: true },
});
return (
<div>
<button onClick={toggleModal}>Toggle Modal</button>
{isModalOpen && (
<div ref={modalRef}>
<h1>Modal</h1>
<button onClick={toggleModal}>Close Modal</button>
</div>
)}
</div>
);
}
Providing types for the modal component.
You can use them as shown below.
View
import type { ModalComponent, ModalComponentProps } from '@vegatality/react-hook-modal';
export const TestModal = ({ name, closeModal, modalRef }: ModalComponentProps<{ name: string }>) => {
return (
<div ref={modalRef}>
<h1>Test Modal</h1>
<p>{name}</p>
<button type='button' onClick={closeModal}>
Close Modal
</button>
</div>
);
};
export const TestModal2: ModalComponent<{ name: string }> = ({ name, closeModal, modalRef }) => {
return (
<div ref={modalRef}>
<h1>Test Modal</h1>
<p>{name}</p>
<button type='button' onClick={closeModal}>
Close Modal
</button>
</div>
);
};
However, you don't have to use the provided modal component types(e.g. ModalComponent, ModalComponentProps).
You can use your own defined types.
View
import { ModalRef } from '@vegatality/react-hook-modal';
interface SomeModalProps {
name: string;
modalRef: ModalRef;
}
const SomeModal = ({ name, modalRef }: SomeModalProps) => {
return (
<div ref={modalRef}>
<h1>Test Modal</h1>
<p>{name}</p>
</div>
);
};
const App = () => {
const { ModalComponentList, openModal, closeModal, watch, destroy, ... } = useModalList();
return (
<div>
<ModalComponentList />
<button onClick={() => openModal({ modalKey: ['some'], ModalComponent: SomeModal, modalProps: { name: 'some' }, options: { resistBackgroundClick: true } })}>
Open Modal 1
</button>
<button onClick={() => openModal({ modalKey: ['some2'], ModalComponent: SomeModal2, modalProps: { name: 'some2' }, options: { resistBackgroundClick: false, scrollable: false } })}>
Open Modal 2
</button>
<button onClick={() => closeModal({ modalKey: ['some2'] })}>Close Modal 2</button>
</div>
);
};