Files
supabase/apps/studio/hooks/ui/usePreventNavigationOnUnsavedChanges.ts
Gildas Garcia ff34a6753c chore: remove unnecessary <PreventNavigationOnUnsavedChanges> (#46763)
## Problem

Since the refactor done in #43900, the
`<PreventNavigationOnUnsavedChanges>` does not bring much value.

## Solution

Remove `PreventNavigationOnUnsavedChanges` and update consumers to
leverage `usePreventNavigationOnUnsavedChanges` and
`DiscardChangesConfirmationDialog`

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Improved the architecture of unsaved-changes navigation handling
across multiple features. Components now use a more modular hook-based
approach for better code organization and consistency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-09 15:30:18 +02:00

91 lines
2.8 KiB
TypeScript

import { useRouter } from 'next/router'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { BASE_PATH } from '@/lib/constants'
interface UsePreventNavigationOnUnsavedChangesOptions {
/*
* Boolean indicating whether there are changes that would be lost if users navigate to another
* page or close the browser tab
*/
hasChanges: boolean
}
interface UsePreventNavigationOnUnsavedChangesReturn {
/*
* Cancel the navigation and keep the changes
*/
handleCancelNavigation: () => void
/*
* Confirm the navigation and lose the changes
*/
handleConfirmNavigation: () => void
/*
* Boolean indicating whether UI to request users confirmation for the navigation should be
* displayed
*/
shouldConfirmNavigation: boolean
}
/*
* Hook that prevents navigation when users could lose their changes.
* It prevents both NextJS and browser navigation (such as when closing the tab)
*/
export const usePreventNavigationOnUnsavedChanges = ({
hasChanges,
}: UsePreventNavigationOnUnsavedChangesOptions): UsePreventNavigationOnUnsavedChangesReturn => {
const router = useRouter()
const [navigateUrl, setNavigateUrl] = useState<string>()
const [confirmNavigate, setConfirmNavigate] = useState(false)
useEffect(() => {
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (hasChanges) {
e.preventDefault()
e.returnValue = '' // deprecated, but older browsers still require this
}
}
const handleBrowseAway = (url: string) => {
if (hasChanges && !confirmNavigate) {
setNavigateUrl(url)
throw 'Route change declined' // Just to prevent the route change
return
}
setNavigateUrl(undefined)
}
window.addEventListener('beforeunload', handleBeforeUnload)
router.events.on('routeChangeStart', handleBrowseAway)
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload)
router.events.off('routeChangeStart', handleBrowseAway)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [confirmNavigate, hasChanges])
const handleCancelNavigation = useCallback(() => {
setNavigateUrl(undefined)
}, [])
const handleConfirmNavigation = useCallback(() => {
setConfirmNavigate(true)
let urlToNavigate = navigateUrl ?? '/'
if (BASE_PATH && urlToNavigate.startsWith(BASE_PATH)) {
urlToNavigate = urlToNavigate.slice(BASE_PATH.length) || '/'
}
if (!urlToNavigate.startsWith('/')) urlToNavigate = `/${urlToNavigate}`
setNavigateUrl(undefined)
router.push(urlToNavigate)
}, [navigateUrl, router])
return useMemo(
() => ({
handleCancelNavigation,
handleConfirmNavigation,
shouldConfirmNavigation: !!navigateUrl,
}),
[navigateUrl, handleCancelNavigation, handleConfirmNavigation]
)
}