useWarnIfUnsavedChanges

PreviousNext

A React hook that warns users before leaving the page when there are unsaved changes.

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

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

ParameterTypeDefaultDescription
unsavedboolean-Whether there are unsaved changes
messagestring"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 becomes false

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