You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Router v6.8自定义模态框拦截导航报错求助

解决React Router路由阻塞报错"A router only supports one blocker at a time"

这个报错的核心原因是同一时间只能注册一个路由阻塞器(blocker),如果在多个组件里分别调用useBlocker/usePrompt,或者组件重复挂载导致重复注册,就会触发该错误。以下是针对性的解决方法:

统一管理路由阻塞逻辑

将路由阻塞的逻辑集中到一个顶层组件中,避免多处注册阻塞器。通过状态控制是否需要拦截跳转,并结合自定义模态框实现确认流程:

实现步骤

  1. 创建全局路由阻塞组件,统一处理跳转拦截逻辑
  2. 通过状态维护未保存更改标识、模态框显示状态以及待跳转的目标位置
  3. 在useBlocker回调中判断是否需要拦截,拦截时打开模态框并记录目标路径
  4. 模态框的确认/取消按钮分别处理跳转或取消拦截操作

示例代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 13:33:35