Documentation Index
Fetch the complete documentation index at: https://mintlify.com/remix-run/react-router/llms.txt
Use this file to discover all available pages before exploring further.
useBeforeUnload
Sets up a callback to be fired on the window’s beforeunload event. This is useful for saving data to localStorage or showing a confirmation dialog before the user leaves the page.
import { useBeforeUnload } from "react-router";
function MyForm() {
useBeforeUnload(
React.useCallback(() => {
localStorage.setItem("unsavedData", formData);
}, [formData])
);
}
Parameters
callback
(event: BeforeUnloadEvent) => any
required
The callback function to execute when the beforeunload event fires. The function receives a BeforeUnloadEvent object.To show the browser’s confirmation dialog, set event.returnValue to a string:useBeforeUnload((event) => {
event.preventDefault();
event.returnValue = ""; // Shows browser confirmation dialog
});
If true, the event will be captured during the capture phase instead of the bubbling phase.
Type Declaration
declare function useBeforeUnload(
callback: (event: BeforeUnloadEvent) => any,
options?: { capture?: boolean }
): void;
Usage Examples
import { useBeforeUnload } from "react-router";
import { useState, useCallback } from "react";
function ArticleEditor() {
const [content, setContent] = useState("");
useBeforeUnload(
useCallback(() => {
// Save draft to localStorage before page unloads
localStorage.setItem("article-draft", content);
}, [content])
);
return (
<form>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Write your article..."
/>
<button type="submit">Publish</button>
</form>
);
}
Warn About Unsaved Changes
import { useBeforeUnload } from "react-router";
import { useState, useCallback } from "react";
function FormWithUnsavedChanges() {
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const [formData, setFormData] = useState({ name: "", email: "" });
useBeforeUnload(
useCallback(
(event) => {
if (hasUnsavedChanges) {
event.preventDefault();
event.returnValue = ""; // Shows browser's default confirmation dialog
}
},
[hasUnsavedChanges]
)
);
return (
<form
onChange={() => setHasUnsavedChanges(true)}
onSubmit={() => setHasUnsavedChanges(false)}
>
<input
name="name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
/>
<input
name="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
/>
<button type="submit">Save</button>
</form>
);
}
Analytics Before Page Unload
import { useBeforeUnload } from "react-router";
import { useCallback } from "react";
function PageWithAnalytics() {
useBeforeUnload(
useCallback(() => {
// Send analytics data before user leaves
navigator.sendBeacon("/api/analytics", JSON.stringify({
event: "page_unload",
timestamp: Date.now(),
pathname: window.location.pathname,
}));
}, [])
);
return <div>{/* Page content */}</div>;
}
Persist User Session Data
import { useBeforeUnload } from "react-router";
import { useCallback } from "react";
function UserSession({ sessionData }: { sessionData: any }) {
useBeforeUnload(
useCallback(() => {
// Save session state before refresh/close
sessionStorage.setItem("user-session", JSON.stringify(sessionData));
}, [sessionData])
);
return <div>{/* Session UI */}</div>;
}
Common Patterns
Conditional Warning
import { useBeforeUnload } from "react-router";
import { useState, useCallback } from "react";
function ConditionalWarning() {
const [isDirty, setIsDirty] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
useBeforeUnload(
useCallback(
(event) => {
// Only warn if form is dirty and not currently submitting
if (isDirty && !isSubmitting) {
event.preventDefault();
event.returnValue = "";
}
},
[isDirty, isSubmitting]
)
);
return <div>{/* Form */}</div>;
}
Combining with useBlocker
import { useBeforeUnload, useBlocker } from "react-router";
import { useState, useCallback } from "react";
function ProtectedForm() {
const [formState, setFormState] = useState({ modified: false });
// Block in-app navigation
const blocker = useBlocker(
({ currentLocation, nextLocation }) =>
formState.modified &&
currentLocation.pathname !== nextLocation.pathname
);
// Warn on page reload/close
useBeforeUnload(
useCallback(
(event) => {
if (formState.modified) {
event.preventDefault();
event.returnValue = "";
}
},
[formState.modified]
)
);
return <div>{/* Form with both protections */}</div>;
}
Auto-save Before Unload
import { useBeforeUnload } from "react-router";
import { useState, useCallback } from "react";
function AutoSaveEditor() {
const [content, setContent] = useState("");
const [lastSaved, setLastSaved] = useState<Date | null>(null);
const saveContent = useCallback(async () => {
await fetch("/api/save", {
method: "POST",
body: JSON.stringify({ content }),
});
setLastSaved(new Date());
}, [content]);
useBeforeUnload(
useCallback(() => {
// Use sendBeacon for reliable delivery
const data = new FormData();
data.append("content", content);
navigator.sendBeacon("/api/save", data);
}, [content])
);
return (
<div>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
/>
{lastSaved && <p>Last saved: {lastSaved.toLocaleTimeString()}</p>}
</div>
);
}
Important Notes
Browser Behavior
- Custom messages are not supported: Modern browsers ignore custom messages and show their own default warning dialog
- Not all browsers support it consistently: Some browsers may not fire the event in certain conditions
- Mobile browsers: May not support
beforeunload events reliably
Best Practices
-
Use
useCallback: Wrap your callback in useCallback to avoid adding/removing event listeners on every render
const callback = useCallback(() => {
// Save data
}, [dependencies]);
useBeforeUnload(callback);
-
Use
navigator.sendBeacon: For sending data, use sendBeacon instead of fetch for better reliability
useBeforeUnload(useCallback(() => {
navigator.sendBeacon("/api/analytics", data);
}, []));
-
Don’t rely on it for critical operations: The event may not always fire (crashes, force quit, etc.)
-
Keep callbacks lightweight: The browser may kill slow operations
Event Limitations
// ❌ This custom message will be ignored by modern browsers
useBeforeUnload((event) => {
event.returnValue = "Custom message"; // Ignored!
});
// ✅ This will show the browser's default dialog
useBeforeUnload((event) => {
event.preventDefault();
event.returnValue = ""; // Shows default dialog
});