React中表单未保存时阻止页面刷新与路由跳转
处理未保存表单状态下的React路由跳转拦截(React Router v6.4.4)
问题背景
开发React应用时,需要在表单内容处于未保存(dirty)状态时阻止用户跳转到其他页面。现有代码仅能拦截页面刷新/关闭操作,但无法阻止React Router的单页路由跳转,且由于使用React Router v6.4.4,无法使用Prompt、useHistory或useBlocking等废弃/移除的API。
解决方案
利用React Router v6.4+提供的useNavigation钩子,结合原有的beforeunload监听,同时实现页面刷新和路由跳转的双重拦截。
修改后的自定义Hook代码
import { useEffect, useState } from "react"; import { useNavigation } from "react-router-dom"; const useUnsavedChanges = () => { const [isDirty, setDirty] = useState(false); const navigation = useNavigation(); useEffect(() => { // 拦截页面刷新/关闭 function confirmPageUnload(event) { if (isDirty) { event.preventDefault(); event.returnValue = "Are you sure you want to leave? Your changes will be lost."; } } window.addEventListener("beforeunload", confirmPageUnload); // 拦截React路由跳转 let unblock; if (isDirty) { unblock = navigation.block((tx) => { const confirmLeave = window.confirm("Are you sure you want to leave? Your changes will be lost."); if (confirmLeave) { unblock(); tx.retry(); } }); } // 清理副作用 return () => { window.removeEventListener("beforeunload", confirmPageUnload); if (unblock) unblock(); }; }, [isDirty, navigation]); const onDirty = () => setDirty(true); const onPristine = () => setDirty(false); return { onDirty, onPristine, isDirty }; }; export default useUnsavedChanges;
代码说明
useNavigation钩子:获取React Router的导航实例,通过其block方法拦截路由跳转动作。- 路由拦截逻辑:当表单处于dirty状态时,调用
navigation.block()注册拦截回调。用户触发路由跳转时,会弹出确认框:- 若用户确认离开,调用
unblock()取消拦截,再通过tx.retry()执行原导航动作; - 若用户取消,则导航被阻止。
- 若用户确认离开,调用
- 副作用清理:组件卸载或
isDirty状态变化时,移除beforeunload监听并取消路由拦截,避免内存泄漏。
组件使用示例
在表单组件中引入并使用该Hook,在表单内容变化时标记为dirty,保存/重置后恢复为pristine:
import { useUnsavedChanges } from './useUnsavedChanges'; function MyForm() { const { onDirty, onPristine } = useUnsavedChanges(); const handleInputChange = () => { onDirty(); }; const handleSubmit = async (e) => { e.preventDefault(); // 执行表单提交逻辑 // await submitForm(); onPristine(); }; const handleReset = () => { // 执行表单重置逻辑 onPristine(); }; return ( <form onSubmit={handleSubmit}> <input type="text" onChange={handleInputChange} placeholder="输入内容..." /> <button type="submit">保存</button> <button type="button" onClick={handleReset}>重置</button> </form> ); }
注意事项
- 确保使用该Hook的组件处于
RouterProvider的上下文范围内(React Router v6.4+推荐的路由配置方式); - 该方案能拦截所有React Router发起的导航,包括
Link点击、navigate函数调用、Form提交等场景。
内容的提问来源于stack exchange,提问作者Ghayas Ud Din
相关产品推荐
相关产品推荐

