React Router v6.8自定义模态框拦截导航报错求助
解决React Router路由阻塞报错"A router only supports one blocker at a time"
这个报错的核心原因是同一时间只能注册一个路由阻塞器(blocker),如果在多个组件里分别调用useBlocker/usePrompt,或者组件重复挂载导致重复注册,就会触发该错误。以下是针对性的解决方法:
统一管理路由阻塞逻辑
将路由阻塞的逻辑集中到一个顶层组件中,避免多处注册阻塞器。通过状态控制是否需要拦截跳转,并结合自定义模态框实现确认流程:
实现步骤
- 创建全局路由阻塞组件,统一处理跳转拦截逻辑
- 通过状态维护未保存更改标识、模态框显示状态以及待跳转的目标位置
- 在
useBlocker回调中判断是否需要拦截,拦截时打开模态框并记录目标路径 - 模态框的确认/取消按钮分别处理跳转或取消拦截操作
示例代码
import { useBlocker, useNavigate, useLocation } from 'react-router-dom'; import { useState, useEffect } from 'react'; // 全局路由阻塞组件 function RouteBlocker({ children, hasUnsavedChanges }) { const navigate = useNavigate(); const location = useLocation(); const [showConfirmModal, setShowConfirmModal] = useState(false); const [pendingTarget, setPendingTarget] = useState(null); // 注册路由阻塞器 const blocker = useBlocker((tx) => { // 仅当存在未保存更改,且跳转目标不是当前页面时触发拦截 if (hasUnsavedChanges && tx.location.pathname !== location.pathname) { setPendingTarget(tx.location); setShowConfirmModal(true); return false; // 阻止默认跳转行为 } return true; // 允许正常跳转 }); // 确认后执行跳转 useEffect(() => { if (pendingTarget && !showConfirmModal) { navigate(pendingTarget); setPendingTarget(null); } }, [pendingTarget, showConfirmModal, navigate]); // 处理确认离开操作 const handleConfirmLeave = () => { setShowConfirmModal(false); }; // 处理取消离开操作 const handleCancelLeave = () => { setShowConfirmModal(false); setPendingTarget(null); blocker.reset(); // 重置阻塞器,恢复正常路由操作 }; return ( <> {children} {/* 自定义确认模态框 */} {showConfirmModal && ( <div style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <div style={{ background: 'white', padding: '2rem', borderRadius: '8px' }}> <p>你有未保存的更改,确定要离开当前页面吗?</p> <div style={{ marginTop: '1rem', display: 'flex', gap: '1rem' }}> <button onClick={handleConfirmLeave}>确定离开</button> <button onClick={handleCancelLeave}>取消</button> </div> </div> </div> )} </> ); } // 在App中使用该组件 function App() { // 维护未保存更改的状态,可由表单组件更新此状态 const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); return ( <Router> <RouteBlocker hasUnsavedChanges={hasUnsavedChanges}> <Routes> <Route path="/" element={<FormPage onUnsavedChange={setHasUnsavedChanges} />} /> <Route path="/other" element={<OtherPage />} /> </Routes> </RouteBlocker> </Router> ); }
关键注意事项
- 避免多处注册阻塞器:确保整个应用中只有一个
useBlocker调用,所有跳转拦截逻辑都通过这个全局组件处理。 - 精准控制拦截时机:在
useBlocker的回调中添加条件判断,只在存在未保存更改且跳转目标不是当前页面时才拦截,避免不必要的阻塞。 - 重置阻塞器:取消离开时调用
blocker.reset(),确保后续路由操作不受影响。
内容的提问来源于stack exchange,提问作者Tiffany
相关产品推荐
相关产品推荐

