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.
Key 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 const 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 const 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>(); return ( <> <p>result: {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 const 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 const 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 const Example = () => { return ( <OverlayProvider> <App /> </OverlayProvider> ); };