A React context provider for Next.js that handles unsaved changes with elegant toasts, drawers, and modals.
- Desktop Toast - Floating toast at bottom center with Save/Discard actions
- Mobile Bottom Drawer - Slides up from bottom on small screens
- Navigation Modal - Confirmation dialog for cross-page navigation
- Browser Protection - Native
beforeunloadprompt on page refresh - Link Interception - Automatically handles
next/linkcomponents - History API - Intercepts
pushStatefor programmatic navigation
Copy the component to your project:
cp components/unsave-provider.tsx your-project/components/npx shadcn@latest add https://raw.githubusercontent.com/gluer-space/unsave-provider/main/public/r/registry.json// app/layout.tsx
import { UnsavedChangesProvider } from "@/components/unsave-provider";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<UnsavedChangesProvider>{children}</UnsavedChangesProvider>
</body>
</html>
);
}"use client";
import { useUnsavedChanges } from "@/components/unsave-provider";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
export function EditableForm() {
const [title, setTitle] = useState("");
const { setDirty, isDirty } = useUnsavedChanges({
onSave: async () => {
await saveData({ title });
},
onCancel: () => {
setTitle("");
},
});
return (
<form>
<Input
value={title}
onChange={(e) => {
setTitle(e.target.value);
setDirty(true);
}}
/>
{isDirty && <p>You have unsaved changes</p>}
</form>
);
}| Prop | Type | Description |
|---|---|---|
onSave |
() => Promise<void> | void |
Callback when user clicks Save |
onCancel |
() => void |
Optional cleanup callback on discard |
| Return | Type | Description |
|---|---|---|
setDirty |
(dirty: boolean) => void |
Mark form as modified or clean |
isDirty |
boolean |
Current dirty state |
| Action | Result |
|---|---|
| Edit form → Same page | Shows toast (desktop) or drawer (mobile) |
| Edit form → Click link | Shows confirmation modal |
Edit form → router.push() |
Shows confirmation modal |
Edit form → history.pushState() |
Shows confirmation modal |
| Edit form → Refresh/Close tab | Browser's native prompt |
- React 18+
- Next.js (App Router)
- motion for animations
- shadcn/ui components (Button, Dialog)
npm run devOpen http://localhost:3000 to see the demo.
