Skip to content

Instantly share code, notes, and snippets.

@SyedTayyabUlMazhar
Created July 1, 2025 14:30
Show Gist options
  • Save SyedTayyabUlMazhar/b360b9523969335c11069027d0ee8fa1 to your computer and use it in GitHub Desktop.
Save SyedTayyabUlMazhar/b360b9523969335c11069027d0ee8fa1 to your computer and use it in GitHub Desktop.
Prevent reload or route change when a form has changes(react-hook-form)
import { DeleteConfirmationModal } from "@/common/components/DeleteConfirmationModal";
import { useBoolean } from "@/common/hooks";
import { useCallback, useEffect } from "react";
import { FieldValues, FormProviderProps } from "react-hook-form";
import { BlockerFunction, useBlocker } from "react-router-dom";
import { Form } from "../ui/form";
export const BlockerForm = <
TFieldValues extends FieldValues,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TContext = any,
TTransformedValues extends FieldValues | undefined = undefined
>(
props: FormProviderProps<TFieldValues, TContext, TTransformedValues>
) => {
const formState = props.formState;
const shouldBlock = useCallback<BlockerFunction>(() => {
// console.log("DEBUG: shouldBlock", formState.isDirty);
return formState.isDirty;
}, [formState.isDirty]);
const blocker = useBlocker(shouldBlock);
const blockerModalState = useBoolean();
useEffect(() => {
// console.log("DEBUG: blocker", blocker);
blockerModalState.set(blocker.state === "blocked");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [blocker]);
useEffect(() => {
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
console.log("DEBUG: handleBeforeUnload", formState.isDirty);
if (formState.isDirty) {
e.preventDefault();
e.returnValue = ""; // Chrome requires returnValue to be set
return ""; // For older browsers
}
};
window.onbeforeunload = handleBeforeUnload;
return () => {
window.onbeforeunload = null;
};
}, [formState.isDirty]); // Watch isDirty instead of the entire form object
return (
<>
{/* For debugging */}
{/* <span className="text-xs text-gray-500">isDirty: {formState.isDirty.toString()}</span> */}
<Form {...props} />
<DeleteConfirmationModal
modalState={blockerModalState}
description="areYouSureYouWantToDiscardTheChanges"
onSubmit={() => {
blocker.proceed?.();
}}
onCancel={() => {
blocker.reset?.();
}}
/>
</>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment