Introduction to overlay-kit
overlay-kit is a library for declaratively managing overlays like modals, popups, and dialogs in React.
You can efficiently implement overlays without complex state management or unnecessary event handling.
Installation
npm install overlay-kitKey Features
Declarative API
- Define overlay UI and behavior declaratively with simple code.
- Reduce state management and event handling code for easier development.
Promise-based API
- Return overlay results as
Promises. - Handle user input results or combine with asynchronous logic.
Extensible Components
- Freely define your desired overlay components.
- Use with various UI libraries.
Using overlay-kit
Let’s learn how to use overlay-kit with a simple example.
Overlays
Any component that renders above the screen can be an overlay.
Let’s create a simple confirmation dialog using MUI’s Dialog component.
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions';
export function ConfirmDialog({ isOpen, close }) {
return (
<Dialog open={isOpen} onClose={close}>
<DialogTitle>Are you sure you want to continue?</DialogTitle>
<DialogActions>
<Button onClick={close}>No</Button>
<Button onClick={close}>Yes</Button>
</DialogActions>
</Dialog>
);
}Setting Overlay Position
Overlays should be rendered in an appropriate position on the screen. Generally, it’s best to render at the app root.
You can specify the rendering position using OverlayProvider.
import { OverlayProvider } from 'overlay-kit';
import { createRoot } from 'react-dom/client';
export function Example() {
return (
<OverlayProvider>
<App />
</OverlayProvider>
);
}
const root = createRoot(document.getElementById('root'));
root.render(<Example />);Opening Overlays
You can easily open and close overlays using overlay.open. Let’s open the ConfirmDialog we defined above.
import { OverlayProvider, overlay } from 'overlay-kit'; import Button from '@mui/material/Button'; import { ConfirmDialog } from './confirm-dialog'; function App() { return ( <Button onClick={() => { overlay.open(({ isOpen, close }) => { return <ConfirmDialog isOpen={isOpen} close={close} />; }); }} > Open Confirm Dialog </Button> ); } export function Example() { return ( <OverlayProvider> <App /> </OverlayProvider> ); }
Opening Asynchronous Overlays
overlay.openAsync returns results as a Promise. Let’s modify it to return a result when the user selects “Yes” in the dialog.
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions';
export function ConfirmDialog({ isOpen, close, confirm }) {
return (
<Dialog open={isOpen} onClose={close}>
<DialogTitle>Are you sure you want to continue?</DialogTitle>
<DialogActions>
<Button onClick={close}>No</Button>
<Button onClick={confirm}>Yes</Button>
</DialogActions>
</Dialog>
);
}Let’s handle asynchronous operations using overlay.openAsync.
import { useState } from 'react'; import { OverlayProvider, overlay } from 'overlay-kit'; import Button from '@mui/material/Button'; import { ConfirmDialog } from './confirm-dialog'; function App() { const [result, setResult] = useState<boolean | null>(null); return ( <> <p>{result === null ? 'Not selected' : result ? 'Y' : 'N'}</p> <Button onClick={async () => { const result = await overlay.openAsync(({ isOpen, close }) => { return <ConfirmDialog isOpen={isOpen} close={() => close(false)} confirm={() => close(true)} />; }); setResult(result); }} > Open Confirm Dialog </Button> </> ); } export function Example() { return ( <OverlayProvider> <App /> </OverlayProvider> ); }
Closing Overlays
overlay.open and overlay.openAsync provide close and unmount functions as arguments to the callback function.
These two functions both close the overlay, but they work differently.
close and unmount difference
Let’s see the difference with an example.
import { OverlayProvider, overlay } from 'overlay-kit'; import Button from '@mui/material/Button'; import { ConfirmDialog } from './confirm-dialog'; function App() { return ( <> <Button onClick={() => { overlay.open( ({ isOpen, close }) => { return <ConfirmDialog isOpen={isOpen} close={close} />; }, { overlayId: 'close-overlay' } ); }} > Close overlay using close </Button> <Button onClick={() => { overlay.open( ({ isOpen, unmount }) => { return <ConfirmDialog isOpen={isOpen} close={unmount} />; }, { overlayId: 'unmount-overlay' } ); }} > Close overlay using unmount </Button> </> ); } export function Example() { return ( <OverlayProvider> <App /> </OverlayProvider> ); }
-
Using
close- The close animation runs, and the state (count) is retained.
- When reopened, the previous state is restored.
-
Using
unmount- It’s immediately removed from memory, skipping the close animation.
- When reopened, the state is reset.
The reason for this difference is that close retains the overlay information in memory so that the close animation can be shown. If a close animation is needed, close should be used first, then unmount should be used.
Releasing Overlay Memory
close retains the overlay information in memory after the close animation. For this reason, if many overlays are opened and closed at the same time, memory leaks may occur.
To avoid this, you should call unmount after the close animation ends to release memory. If there’s a callback function that runs after the overlay ends, such as the onExit prop, you can pass the unmount function to release memory.
MUI doesn’t provide the onExit prop, but you can use React’s useEffect cleanup function to automatically release memory when the overlay closes.
import { useEffect } from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions';
export function ConfirmDialog({ isOpen, close, onExit }) {
useEffect(() => {
return () => onExit();
}, []);
return (
<Dialog open={isOpen} onClose={close}>
<DialogTitle>Are you sure you want to continue?</DialogTitle>
<DialogActions>
<Button onClick={close}>No</Button>
<Button onClick={close}>Yes</Button>
</DialogActions>
</Dialog>
);
}import { OverlayProvider, overlay } from 'overlay-kit'; import Button from '@mui/material/Button'; import { ConfirmDialog } from './confirm-dialog'; function App() { return ( <> <Button onClick={() => { overlay.open(({ isOpen, close, unmount }) => { return <ConfirmDialog isOpen={isOpen} close={close} onExit={unmount} />; }); }} > Open Confirm Dialog </Button> </> ); } export function Example() { return ( <OverlayProvider> <App /> </OverlayProvider> ); }