11"use client" ;
22
3- import { useCallback , useEffect , useRef } from "react" ;
3+ import { useCallback , useEffect } from "react" ;
44import { useDialogs } from "@/components/ui/dialog-provider" ;
55
66const LEAVE_MESSAGE = "You have unsaved changes. Leave this page without saving?" ;
77
8- // Next.js internally sets an auto-incrementing `idx` on history.state to track
9- // navigation position. This is an undocumented internal — not a public API — so
10- // it may disappear or change in any Next.js release. We use it to determine
11- // whether the user navigated forward or back so we can revert the navigation
12- // when they cancel. If `idx` is absent we degrade gracefully (warn, but can't
13- // revert). See: https://github.com/vercel/next.js/discussions/34980
14- function getCurrentHistoryIndex ( ) {
15- const historyState = window . history . state as { idx ?: number } | null ;
16- return typeof historyState ?. idx === "number" ? historyState . idx : null ;
17- }
18-
198export function useUnsavedChangesGuard ( isDirty : boolean ) {
209 const { confirm } = useDialogs ( ) ;
21- const historyIndexRef = useRef < number | null > ( null ) ;
22- const isRevertingNavigationRef = useRef ( false ) ;
2310
2411 const confirmDiscardChanges = useCallback ( async ( ) => {
2512 if ( ! isDirty ) {
@@ -38,47 +25,34 @@ export function useUnsavedChangesGuard(isDirty: boolean) {
3825 return ;
3926 }
4027
41- historyIndexRef . current = getCurrentHistoryIndex ( ) ;
42- isRevertingNavigationRef . current = false ;
43-
4428 const handleBeforeUnload = ( event : BeforeUnloadEvent ) => {
4529 event . preventDefault ( ) ;
4630 // Needed for Chrome version <=119 to show the confirmation dialog
4731 event . returnValue = true ;
4832 } ;
4933
50- const handlePopState = ( ) => {
51- const nextHistoryIndex = getCurrentHistoryIndex ( ) ;
52-
53- if ( ! isRevertingNavigationRef . current ) {
54- const shouldLeave = window . confirm ( LEAVE_MESSAGE ) ;
34+ const handleHistoryNavigate = ( event : NavigateEvent ) => {
35+ if ( event . navigationType !== "traverse" || ! event . cancelable || event . hashChange ) {
36+ return ;
37+ }
5538
56- if ( ! shouldLeave ) {
57- if (
58- historyIndexRef . current !== null &&
59- nextHistoryIndex !== null &&
60- historyIndexRef . current !== nextHistoryIndex
61- ) {
62- isRevertingNavigationRef . current = true ;
63- window . history . go ( historyIndexRef . current > nextHistoryIndex ? 1 : - 1 ) ;
64- return ;
65- }
39+ const destination = new URL ( event . destination . url ) ;
6640
67- console . warn ( "Unable to determine history direction for unsaved-changes guard" ) ;
68- }
41+ if ( destination . origin !== window . location . origin ) {
42+ return ;
6943 }
7044
71- isRevertingNavigationRef . current = false ;
72- historyIndexRef . current = nextHistoryIndex ;
45+ if ( ! window . confirm ( LEAVE_MESSAGE ) ) {
46+ event . preventDefault ( ) ;
47+ }
7348 } ;
7449
7550 window . addEventListener ( "beforeunload" , handleBeforeUnload ) ;
76- window . addEventListener ( "popstate " , handlePopState ) ;
51+ window . navigation ?. addEventListener ( "navigate " , handleHistoryNavigate ) ;
7752
7853 return ( ) => {
79- isRevertingNavigationRef . current = false ;
8054 window . removeEventListener ( "beforeunload" , handleBeforeUnload ) ;
81- window . removeEventListener ( "popstate " , handlePopState ) ;
55+ window . navigation ?. removeEventListener ( "navigate " , handleHistoryNavigate ) ;
8256 } ;
8357 } , [ isDirty ] ) ;
8458
0 commit comments