unsaveChangeWarning hooks

This commit is contained in:
hamid zarghami
2025-10-13 10:48:12 +03:30
parent 5b376b83d1
commit d3d63a922c
3 changed files with 85 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
import { useEffect, useRef } from "react";
import { useLocation } from "react-router-dom";
/**
* هوک برای هشدار به کاربر هنگام خروج از صفحه با تغییرات ذخیره نشده
* این هوک از رفرش، بستن تب، و navigation داخلی (دکمه برگشت، لینک‌ها) جلوگیری می‌کند
* @param isDirty - آیا فرم تغییر کرده است؟
* @param message - پیام هشدار (اختیاری)
*/
export const useUnsavedChangesWarning = (
isDirty: boolean,
message: string = "تغییرات شما ذخیره نشده‌اند. آیا مطمئن هستید که می‌خواهید این صفحه را ترک کنید؟"
) => {
const location = useLocation();
const currentPath = useRef(location.pathname);
const isNavigatingRef = useRef(false);
// برای جلوگیری از بستن/رفرش مرورگر
useEffect(() => {
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (isDirty) {
e.preventDefault();
e.returnValue = "";
return "";
}
};
if (isDirty) {
window.addEventListener("beforeunload", handleBeforeUnload);
}
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
};
}, [isDirty]);
// برای جلوگیری از navigation داخلی (دکمه برگشت مرورگر)
useEffect(() => {
if (!isDirty) {
currentPath.current = location.pathname;
return;
}
const handlePopState = () => {
if (isDirty && !isNavigatingRef.current) {
const confirmLeave = window.confirm(message);
if (!confirmLeave) {
// اگر کاربر لغو کرد، به صفحه قبلی برگرد
window.history.pushState(null, "", currentPath.current);
} else {
// اگر کاربر تایید کرد، اجازه navigation بده
currentPath.current = location.pathname;
}
}
};
// یک state جدید به history اضافه کن تا بتونیم popstate رو catch کنیم
window.history.pushState(null, "", location.pathname);
window.addEventListener("popstate", handlePopState);
return () => {
window.removeEventListener("popstate", handlePopState);
};
}, [isDirty, message, location.pathname]);
// Update current path when location changes
useEffect(() => {
if (!isDirty) {
currentPath.current = location.pathname;
}
}, [location.pathname, isDirty]);
};
+5
View File
@@ -16,11 +16,13 @@ import { toast } from 'react-toastify'
import { ErrorType } from '../../helpers/types'
import { Pages } from '../../config/Pages'
import { useNavigate } from 'react-router-dom'
import { useUnsavedChangesWarning } from '../../hooks/useUnsavedChangesWarning'
const CreateBlog: FC = () => {
const { t } = useTranslation('global')
const [file, setFile] = useState<File>()
const [audioFile, setAudioFile] = useState<File>()
const [isSaved, setIsSaved] = useState(false)
const singleSlug = useSingleUpload()
const audioUpload = useSingleUpload()
@@ -85,6 +87,7 @@ const CreateBlog: FC = () => {
createBlog.mutate(values, {
onSuccess() {
setIsSaved(true)
toast.success(t('success'))
navigate(Pages.blog.list)
},
@@ -95,6 +98,8 @@ const CreateBlog: FC = () => {
},
})
// استفاده از hook برای هشدار تغییرات ذخیره نشده
useUnsavedChangesWarning(formik.dirty && !isSaved)
return (
<div className='mt-4'>
+6
View File
@@ -16,12 +16,14 @@ import { toast } from 'react-toastify'
import { ErrorType } from '../../helpers/types'
import { Pages } from '../../config/Pages'
import { useNavigate, useParams } from 'react-router-dom'
import { useUnsavedChangesWarning } from '../../hooks/useUnsavedChangesWarning'
const UpdateBlog: FC = () => {
const { id } = useParams()
const { t } = useTranslation('global')
const [file, setFile] = useState<File>()
const [audioFile, setAudioFile] = useState<File>()
const [isSaved, setIsSaved] = useState(false)
const singleSlug = useSingleUpload()
const audioUpload = useSingleUpload()
const getBlogDetail = useGetBlogDetail(id || '')
@@ -84,6 +86,7 @@ const UpdateBlog: FC = () => {
updateBlog.mutate({ id: id || '', params: values }, {
onSuccess() {
setIsSaved(true)
toast.success(t('success'))
navigate(Pages.blog.list)
},
@@ -102,6 +105,9 @@ const UpdateBlog: FC = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getBlogDetail.data])
// استفاده از hook برای هشدار تغییرات ذخیره نشده
useUnsavedChangesWarning(formik.dirty && !isSaved)
return (
<div className='mt-4'>