React Router v6+Antd Form中阻止页面返回/刷新并弹出未保存提示
问题描述
使用Antd Form开发表单页面,需求是用户刷新页面或点击浏览器返回按钮时,若表单存在未保存修改则弹出提示。目前刷新页面的提示功能正常,但点击浏览器返回按钮无任何响应,尝试多种基于history的实现方式均无效,使用的是React Router v6。
原因分析
- 自行创建的
createBrowserHistory()实例和React Router v6内部维护的history实例不是同一个,因此调用该实例的block方法无法监听React Router的路由变化(包括返回按钮触发的POP动作)。 - React Router v6移除了
useHistory的block方法,原有的history.block用法不再适用。
解决方案
方案一:使用React Router v6的unstable_block(临时方案,可等待稳定API)
React Router v6提供了不稳定的unstable_block方法,可通过useNavigation钩子获取:
import React, { useEffect } from 'react'; import { Form, Input, Button } from 'antd'; import { useNavigate, useNavigation } from 'react-router-dom'; const ExampleForm = () => { const [form] = Form.useForm(); const navigate = useNavigate(); const navigation = useNavigation(); useEffect(() => { // 监听路由变化,拦截未保存的修改 const unblock = navigation.unstable_block((tx) => { if (form.isFieldsTouched(true)) { // true表示忽略初始值,只要有修改就算 const confirmLeave = window.confirm('你有未保存的修改,确定要离开吗?'); if (confirmLeave) { unblock(); // 解除拦截,允许跳转 tx.retry(); // 重试路由跳转 } return false; // 拦截当前跳转 } return true; // 允许跳转 }); return () => unblock(); // 组件卸载时解除拦截 }, [form, navigation]); const handleSubmit = () => { form.validateFields().then(values => { // 处理表单提交逻辑 console.log(values); form.resetFields(); navigate('/'); }); }; return ( <Form form={form} onFinish={handleSubmit}> <Form.Item label="Name" name="name" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item label="Email" name="email" rules={[{ required: true, type: 'email' }]}> <Input /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); }; export default ExampleForm;
方案二:监听popstate事件(稳定方案)
通过监听浏览器的popstate事件处理返回按钮动作,结合Antd Form的状态判断:
import React, { useEffect, useState } from 'react'; import { Form, Input, Button, Modal } from 'antd'; import { useNavigate, useLocation } from 'react-router-dom'; const ExampleForm = () => { const [form] = Form.useForm(); const navigate = useNavigate(); const location = useLocation(); const [isModalVisible, setIsModalVisible] = useState(false); const [lastLocation, setLastLocation] = useState(location); const [isBlocking, setIsBlocking] = useState(false); // 处理刷新页面的提示 useEffect(() => { const handleBeforeUnload = (e) => { if (form.isFieldsTouched(true)) { e.preventDefault(); e.returnValue = ''; } }; window.addEventListener('beforeunload', handleBeforeUnload); return () => window.removeEventListener('beforeunload', handleBeforeUnload); }, [form]); // 监听popstate事件(返回按钮触发) useEffect(() => { const handlePopState = () => { if (form.isFieldsTouched(true) && !isBlocking) { setIsModalVisible(true); // 回退到当前页面,阻止默认返回动作 navigate(lastLocation.pathname); } }; window.addEventListener('popstate', handlePopState); return () => window.removeEventListener('popstate', handlePopState); }, [form, navigate, lastLocation, isBlocking]); // 记录当前路由,用于拦截后恢复 useEffect(() => { if (!isModalVisible) { setLastLocation(location); } }, [location, isModalVisible]); // 确认离开的逻辑 const handleConfirmLeave = () => { setIsBlocking(false); setIsModalVisible(false); // 执行返回动作 navigate(-1); }; // 取消离开,留在当前页面 const handleCancelLeave = () => { setIsBlocking(false); setIsModalVisible(false); }; const handleSubmit = () => { form.validateFields().then(values => { console.log(values); form.resetFields(); navigate('/'); }); }; return ( <> <Form form={form} onFinish={handleSubmit}> <Form.Item label="Name" name="name" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item label="Email" name="email" rules={[{ required: true, type: 'email' }]}> <Input /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> <Modal title="提示" visible={isModalVisible} onOk={handleConfirmLeave} onCancel={handleCancelLeave} okText="确定离开" cancelText="留在当前页面" > <p>你有未保存的修改,确定要离开吗?</p> </Modal> </> ); }; export default ExampleForm;
关键注意点
- 不要自行创建
createBrowserHistory()实例,必须使用React Router提供的导航钩子(useNavigate、useNavigation),确保和路由内部实例一致。 - 使用Antd Form的
form.isFieldsTouched(true)判断表单是否有修改,true参数会忽略初始值,只要用户修改过字段就会返回true。 - 方案二中需要手动处理路由回退和状态恢复,避免出现路由跳转异常。
内容的提问来源于stack exchange,提问作者nrane nrane
相关产品推荐
相关产品推荐

