Added guard to prevent accidental loss when navigating to a different website or closing the browser tab.

This commit is contained in:
Colin Dawson 2026-02-12 16:18:06 +00:00
parent 75f0a9c72e
commit 046869510a

View File

@ -1,4 +1,4 @@
import { useState, useCallback, useRef } from "react";
import { useState, useCallback, useRef, useEffect } from "react";
import Joi from "joi";
import { GeneralIdRef } from "../../utils/GeneralIdRef";
import {
@ -88,11 +88,17 @@ interface UseFormReturn {
handleSsoProviderPickerChange: (name: string, value: GeneralIdRef) => void;
handleToggleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
setState: (updates: Partial<FormState>) => void;
hasUnsavedChanges: () => boolean;
markAsSaved: () => void;
setupNavigationGuard: (onBlock?: (location: Location) => void) => () => void;
}
export const useForm = (initialState: FormState): UseFormReturn => {
const [state, setStateInternal] = useState<FormState>(initialState);
const schemaRef = useRef<joiSchema>({});
const initialDataRef = useRef<FormData>(
JSON.parse(JSON.stringify(initialState.data)),
);
const setState = useCallback((updates: Partial<FormState>) => {
setStateInternal((prev) => ({ ...prev, ...updates }));
@ -564,6 +570,42 @@ export const useForm = (initialState: FormState): UseFormReturn => {
[state.data, validate, setState],
);
// Unsaved changes detection
const hasUnsavedChanges = useCallback((): boolean => {
return (
JSON.stringify(state.data) !== JSON.stringify(initialDataRef.current)
);
}, [state.data]);
const markAsSaved = useCallback((): void => {
initialDataRef.current = JSON.parse(JSON.stringify(state.data));
}, [state.data]);
const setupNavigationGuard = useCallback(
(onBlock?: (location: Location) => void): (() => void) => {
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (hasUnsavedChanges()) {
e.preventDefault();
e.returnValue = "";
}
};
window.addEventListener("beforeunload", handleBeforeUnload);
// Return cleanup function
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
};
},
[hasUnsavedChanges],
);
// Setup navigation guard on mount/unmount
useEffect(() => {
const cleanup = setupNavigationGuard();
return cleanup;
}, [setupNavigationGuard]);
const api: any = {
state,
schema: schemaRef.current,
@ -588,6 +630,9 @@ export const useForm = (initialState: FormState): UseFormReturn => {
handleToggleChange,
handleTasksChange,
setState,
hasUnsavedChanges,
markAsSaved,
setupNavigationGuard,
};
Object.defineProperty(api, "schema", {
get: () => schemaRef.current,