Managing modals in React can be a pain. You know the drill:
- Define
useStatein the parent component. - Pass
isOpenandonOpenChangedown the tree. - Create callback functions to handle results.
- Repeat for every single modal. ๐ฉ
"I just want to call a function to open a modal, like alert() or confirm()."
If you've ever thought this, I built hiraku for you.
It's a new library designed specifically for shadcn/ui (Radix UI) to make modal management simple, type-safe, and imperative.
The Problem: "Declarative" isn't always better
shadcn/ui is amazing. But the standard way of handling dialogs often leads to bloated parent components.
The Old Way:
function ParentComponent() {
// 1. State clutter
const [isOpen, setIsOpen] = useState(false);
// 2. Logic fragmentation
const handleConfirm = () => {
// ... logic ...
setIsOpen(false);
}
return (
<>
<Button onClick={() => setIsOpen(true)}>Open</Button>
<MyDialog
open={isOpen}
onOpenChange={setIsOpen}
onConfirm={handleConfirm}
/>
</>
);
}
This separates the "trigger" logic from the "result" logic. It makes your code harder to read and maintain.
The Solution: Function-based Modals ๐
hiraku lets you treat modals like function calls.
The hiraku Way:
const handleDelete = async () => {
// 1. Open it
await confirmDialog.open({
title: "Delete Item",
message: "Are you sure?",
});
// 2. Await the result right here
const { role } = await confirmDialog.onDidClose();
if (role === "confirm") {
console.log("Item deleted!");
}
};
Clean, linear, and easy to read.
Why hiraku? โจ
- โก๏ธ Zero State Management: No more
useStateorisOpenprops in your parents. - ๐ฏ Intuitive API: Just
createDialog()andopen(). - ๐ช Call from Anywhere: Open modals from hooks, event handlers, or even pure utility functions (like API error handlers).
- ๐ 100% Type-Safe: Props and return values are fully typed.
- ๐งฉ shadcn/ui Ready: Designed to work seamlessly with your existing Radix UI components.
- ๐ชถ Lightweight: ~3KB (gzipped).
Quick Start
1. Install
npm install @hirotoshioi/hiraku
Add the ModalProvider to your app root:
import { ModalProvider } from "@hirotoshioi/hiraku";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
<ModalProvider />
</StrictMode>
);
2. Create a Controller
Wrap your component with createDialog. You don't need the <Dialog> wrapper anymore; hiraku handles it.
import { createDialog } from "@hirotoshioi/hiraku";
interface Props {
title: string;
message: string;
}
// Your component receives props directly
function InfoDialogContent({ title, message }: Props) {
return (
<DialogContent>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{message}</DialogDescription>
</DialogHeader>
<DialogFooter>
{/* Close it via the controller */}
<Button onClick={() => infoDialog.close()}>OK</Button>
</DialogFooter>
</DialogContent>
);
}
// Create the controller
export const infoDialog = createDialog(InfoDialogContent);
3. Open it!
// Fully typed props!
await infoDialog.open({
title: "Hello World",
message: "This is so much easier.",
});
Getting Data Back (Type-Safe!)
You can even define what data the modal returns.
// Define the return type
export const editUserDialog = createDialog(EditUserDialog).returns<User>();
// In your component
const handleSave = (user: User) => {
editUserDialog.close({
role: "confirm",
data: user
});
};
// In your usage
const { data, role } = await editUserDialog.onDidClose();
if (role === "confirm" && data) {
console.log("Updated user:", data);
}
Call Modals from API Utilities
Since hiraku controllers aren't hooks, you can import them anywhere. This is a game-changer for global error handling.
// api.ts
import { errorDialog } from "./dialogs/errorDialog";
export async function fetchData() {
try {
// ... fetch logic
} catch (err) {
// Open a modal directly from your non-React logic!
await errorDialog.open({
title: "Network Error",
message: "Something went wrong.",
});
}
}
Migration is Easy
You don't have to rewrite your whole app. You can migrate one modal at a time. Just remove the isOpen props and wrap the component in createDialog.
Give it a try! ๐
I built hiraku to make my own life easier when working with shadcn/ui, and I hope it helps you too.
Check it out on GitHub and let me know what you think!
๐ https://github.com/HirotoShioi/hiraku
(If you like it, a Star โญ๏ธ would make my day!)
Top comments (0)