Getting Started
Blocks
About
The useWarnIfUnsavedChanges hook provides a simple way to warn users before they leave a page when there are unsaved changes. It handles both page navigation and browser refresh/close events.
Features
- ✅ Browser Navigation: Warns on page refresh, tab close, or browser close
- ✅ SPA Navigation: Intercepts Next.js Link navigation and other anchor clicks
- ✅ Custom Messages: Configurable warning message
- ✅ Automatic Cleanup: Properly removes event listeners on unmount
- ✅ TypeScript: Fully typed for better developer experience
Preview
Simple Form
Type something above, then try to navigate away or refresh the page to see the warning.
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { useWarnIfUnsavedChanges } from "@/hooks/use-warn-if-unsaved-changes"
export function UseWarnIfUnsavedChangesDemo() {
const [inputValue, setInputValue] = useState("")
const hasUnsavedChanges = inputValue.length > 0
// Warn before leaving when there are unsaved changes
useWarnIfUnsavedChanges(
hasUnsavedChanges,
"You have unsaved changes. Are you sure you want to leave?"
)
const handleSave = () => {
// Simulate save and clear
alert("Changes saved! (This is just a demo)")
setInputValue("")
}
const handleReset = () => {
setInputValue("")
}
return (
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle className="flex items-center justify-between">
Simple Form
{hasUnsavedChanges && (
<span className="text-sm font-normal text-amber-600 dark:text-amber-400">
(Unsaved changes)
</span>
)}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="input">Type something</Label>
<Input
id="input"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Start typing to see the warning in action..."
/>
</div>
<div className="flex gap-2">
<Button onClick={handleSave} disabled={!hasUnsavedChanges}>
Save Changes
</Button>
<Button
onClick={handleReset}
variant="outline"
disabled={!hasUnsavedChanges}
>
Reset
</Button>
</div>
<p className="text-muted-foreground text-sm">
Type something above, then try to navigate away or refresh the page to
see the warning.
</p>
</CardContent>
</Card>
)
}
Installation
pnpm dlx shadcn@latest add https://ui.nowts.app/r/use-warn-if-unsaved-changes.json
Usage
Basic usage with form
import { useState } from "react"
import { useWarnIfUnsavedChanges } from "@/hooks/use-warn-if-unsaved-changes"
export function MyForm() {
const [formData, setFormData] = useState("")
const [savedData, setSavedData] = useState("")
const hasUnsavedChanges = formData !== savedData
useWarnIfUnsavedChanges(hasUnsavedChanges)
const handleSave = () => {
setSavedData(formData)
// Your save logic here
}
return (
<form>
<input value={formData} onChange={(e) => setFormData(e.target.value)} />
<button type="button" onClick={handleSave}>
Save
</button>
</form>
)
}
Custom warning message
useWarnIfUnsavedChanges(
hasUnsavedChanges,
"You have unsaved changes that will be lost. Do you want to continue?"
)
With complex form state
import { useEffect, useState } from "react"
import { useWarnIfUnsavedChanges } from "@/hooks/use-warn-if-unsaved-changes"
export function UserProfileForm() {
const [formData, setFormData] = useState({
name: "",
email: "",
bio: "",
})
const [originalData, setOriginalData] = useState({
name: "",
email: "",
bio: "",
})
// Load initial data
useEffect(() => {
const loadUserData = async () => {
const userData = await fetchUserProfile()
setFormData(userData)
setOriginalData(userData)
}
loadUserData()
}, [])
// Check if any field has changed
const hasUnsavedChanges = Object.keys(formData).some(
(key) => formData[key] !== originalData[key]
)
useWarnIfUnsavedChanges(
hasUnsavedChanges,
"Your profile changes haven't been saved. Leave anyway?"
)
const handleSave = async () => {
await saveUserProfile(formData)
setOriginalData(formData) // Reset the baseline
}
return (
<form>
{/* Your form fields */}
<button type="button" onClick={handleSave}>
Save Profile
</button>
</form>
)
}
With auto-save functionality
import { useEffect, useState } from "react"
import { useDebounceFn } from "@/hooks/use-debounce-fn"
import { useWarnIfUnsavedChanges } from "@/hooks/use-warn-if-unsaved-changes"
export function AutoSaveForm() {
const [content, setContent] = useState("")
const [lastSaved, setLastSaved] = useState("")
const [isSaving, setIsSaving] = useState(false)
const hasUnsavedChanges = content !== lastSaved && !isSaving
useWarnIfUnsavedChanges(
hasUnsavedChanges,
"Your document has unsaved changes. Leave without saving?"
)
const debouncedSave = useDebounceFn(async (value: string) => {
setIsSaving(true)
try {
await saveDocument(value)
setLastSaved(value)
} finally {
setIsSaving(false)
}
}, 1000)
useEffect(() => {
if (content !== lastSaved) {
debouncedSave(content)
}
}, [content, lastSaved, debouncedSave])
return (
<div>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Start typing..."
/>
<div className="text-muted-foreground text-sm">
{isSaving
? "Saving..."
: hasUnsavedChanges
? "Unsaved changes"
: "All changes saved"}
</div>
</div>
)
}
API Reference
Parameters
Parameter | Type | Default | Description |
---|---|---|---|
unsaved | boolean | - | Whether there are unsaved changes |
message | string | "Changes you made has not been saved just yet. Do you wish to proceed anyway?" | Custom warning message |
Behavior
- Browser events: Uses
window.onbeforeunload
to catch page refresh, tab close, and browser close - SPA navigation: Intercepts anchor clicks and shows confirmation dialog
- Automatic cleanup: Removes all event listeners when component unmounts or
unsaved
becomesfalse
Notes
- The hook uses
MutationObserver
to dynamically handle anchor elements added to the DOM - For browser navigation events, the actual message shown may vary depending on the browser
- The hook is designed to work with Next.js Link components and other SPA routing solutions
Build Your SaaS in Days, Not Months
NOW.TS is the Next.js 15 boilerplate with everything you need to launch your SaaS—auth, payments, database, and AI-ready infrastructure.
Learn more about NOW.TS