如何在react-router-dom v6的SPA中检测路由变化并提示未保存修改?
React Router v6 路由跳转时检测未保存表单修改并提示警告
在React Router v6中,要实现路由跳转前检测未保存表单修改并弹窗警告,可以通过以下几种方式实现:
方法一:使用官方 usePrompt 钩子(v6.4+)
这是最简便的方案,React Router v6.4及以上版本提供了usePrompt钩子,直接用于触发导航确认提示:
import { usePrompt } from 'react-router-dom'; import { useState } from 'react'; function FormComponent() { // 已有的未保存修改标记,表单变化时更新为true,保存/取消时设为false const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); // 监听表单输入,更新未保存状态 const handleInputChange = () => { setHasUnsavedChanges(true); }; // 保存表单后重置状态 const handleSave = () => { // 执行保存逻辑 setHasUnsavedChanges(false); }; // 取消编辑重置状态 const handleCancel = () => { // 重置表单逻辑 setHasUnsavedChanges(false); }; // 使用usePrompt,当hasUnsavedChanges为true时触发提示 usePrompt({ message: '你有未保存的修改,确定要离开当前页面吗?', when: hasUnsavedChanges }); return ( <form> {/* 表单元素 */} <input type="text" onChange={handleInputChange} /> <button onClick={handleSave}>保存</button> <button onClick={handleCancel}>取消</button> </form> ); }
方法二:使用 useBlocker 实现自定义弹窗(v6.4+)
如果需要替换浏览器默认的确认框,用自定义模态组件,可以使用useBlocker钩子来手动控制导航流程:
import { useBlocker, useNavigation } from 'react-router-dom'; import { useState, useEffect } from 'react'; import CustomModal from './CustomModal'; // 自定义模态组件 function FormComponent() { const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [showConfirmModal, setShowConfirmModal] = useState(false); const blocker = useBlocker(() => hasUnsavedChanges); const navigation = useNavigation(); // 处理表单输入、保存、取消逻辑同方法一 const handleInputChange = () => setHasUnsavedChanges(true); const handleSave = () => { /* 保存逻辑 */ setHasUnsavedChanges(false); }; const handleCancel = () => { /* 重置逻辑 */ setHasUnsavedChanges(false); }; useEffect(() => { // 当导航被拦截时,显示自定义模态框 if (blocker.state === 'blocked') { setShowConfirmModal(true); } }, [blocker.state]); // 确认离开的逻辑 const handleConfirmLeave = () => { setShowConfirmModal(false); blocker.reset(); // 重置拦截状态 // 继续执行导航 navigation.navigate(blocker.location.pathname); }; // 取消离开的逻辑 const handleCancelLeave = () => { setShowConfirmModal(false); blocker.abort(); // 终止导航 }; return ( <> <form> <input type="text" onChange={handleInputChange} /> <button onClick={handleSave}>保存</button> <button onClick={handleCancel}>取消</button> </form> <CustomModal isOpen={showConfirmModal} message="你有未保存的修改,确定要离开吗?" onConfirm={handleConfirmLeave} onCancel={handleCancelLeave} /> </> ); }
方法三:适配React Router v6.4以下版本
如果你的项目使用v6.4之前的版本,可以通过navigate.block方法来实现拦截:
import { useLocation, useNavigate } from 'react-router-dom'; import { useState, useEffect } from 'react'; function FormComponent() { const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const location = useLocation(); const navigate = useNavigate(); // 表单逻辑同前 const handleInputChange = () => setHasUnsavedChanges(true); const handleSave = () => { /* 保存逻辑 */ setHasUnsavedChanges(false); }; const handleCancel = () => { /* 重置逻辑 */ setHasUnsavedChanges(false); }; useEffect(() => { // 注册导航拦截 const unblock = navigate.block((tx) => { if (hasUnsavedChanges) { const shouldLeave = window.confirm('你有未保存的修改,确定要离开吗?'); if (shouldLeave) { unblock(); // 解除拦截 tx.retry(); // 重试导航 } } else { unblock(); // 无未保存修改,直接放行 } }); // 组件卸载时解除拦截 return unblock; }, [hasUnsavedChanges, navigate]); return ( <form> <input type="text" onChange={handleInputChange} /> <button onClick={handleSave}>保存</button> <button onClick={handleCancel}>取消</button> </form> ); }
关键注意点
- 确保
hasUnsavedChanges状态能准确反映表单的修改状态:表单输入变化时设为true,保存/取消操作完成后设为false。 - 组件卸载时要清理拦截逻辑(上述示例中都通过
useEffect的返回值实现了),避免内存泄漏。
内容的提问来源于stack exchange,提问作者Ilya Kruchinin
相关产品推荐
相关产品推荐

