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

浏览器返回按钮触发未保存页面确认模态框无限循环问题

解决未保存更改时返回导航的模态框无限循环问题

问题现象

页面存在未保存更改时,点击浏览器返回按钮或页面「Previous」按钮,确认模态框正常弹出,但确认后模态框持续弹出陷入无限循环;通过其他链接导航则完全正常。若移除window.history.pushState代码,返回导航时模态框又完全不触发。

问题根源

  1. 在popstate事件回调中调用pushState会向浏览器历史栈添加一条当前页面的记录,导致调用navigate(-1)时,实际上只是回到刚添加的这条冗余记录,再次触发popstate事件,形成循环。
  2. 未区分用户主动确认后的导航操作和普通的返回操作,导致确认后的导航仍被拦截逻辑触发。

解决方案

  1. 添加状态标记是否处于确认导航的过程中,避免此时拦截popstate事件
  2. 调整popstate事件的处理逻辑,仅在非确认状态下拦截未保存的返回操作
  3. 优化handleConfirm中的返回导航逻辑,跳过之前pushState添加的冗余历史记录

修改后的代码

页面组件(EstimateRequestPage)

const EstimateRequestPage = () => {
  const navigate = useNavigate();
  const location = useLocation();
  const params = new URLSearchParams(location.search);

  const { isDirty, setIsDirty } = useIsDirty();

  const { handleNavigate, renderModal } = usePreventLeave(isDirty);

  return (
    <Container>
      <form onSubmit={handleSubmit}>
        {/* Form fields... */}
        <ButtonGroup>
          <PreviousButton variant="outlined" onClick={() => handleNavigate(-1)}>
            Previous
          </PreviousButton>
          <RequestButton variant="contained" type="submit">
            Submit
          </RequestButton>
        </ButtonGroup>
      </form>
      {renderModal()}
    </Container>
  );
};

自定义Hook(usePreventLeave)

import { useState, useCallback, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import ConfirmationModal from '@/components/ui/modal/Modal';

export const usePreventLeave = (isDirty) => {
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [pendingPath, setPendingPath] = useState(null);
  const [isConfirming, setIsConfirming] = useState(false); // 标记是否正在确认导航
  const navigate = useNavigate();
  const location = useLocation();

  useEffect(() => {
    const handlePopState = (event) => {
      // 正在确认导航时不触发拦截逻辑
      if (isDirty && !isConfirming) {
        window.history.pushState(null, '', location.pathname + location.search);
        setIsModalOpen(true);
      }
    };
    
    window.history.pushState(null, '', location.pathname + location.search);
    window.addEventListener('popstate', handlePopState);

    return () => {
      window.removeEventListener('popstate', handlePopState);
    };
  }, [isDirty, location.pathname, location.search, isConfirming]);

  const handleNavigate = useCallback(
    (path) => {
      if (isDirty) {
        setPendingPath(path);
        setIsModalOpen(true);
      } else {
        navigate(path);
      }
    },
    [isDirty, navigate]
  );

  const handleConfirm = useCallback(() => {
    setIsModalOpen(false);
    setIsConfirming(true); // 标记为确认状态,跳过后续popstate拦截

    if (pendingPath === null) {
      // 跳过pushState添加的冗余记录,直接回到真正的上一页
      navigate(-2);
    } else if (pendingPath) {
      navigate(pendingPath);
    }

    // 导航完成后重置状态
    setTimeout(() => {
      setIsConfirming(false);
      setPendingPath(null);
    }, 100);
  }, [navigate, pendingPath]);

  const handleCancel = useCallback(() => {
    setIsModalOpen(false);
    setPendingPath(null);
  }, []);

  return {
    handleNavigate,
    renderModal: () => (
      <ConfirmationModal
        open={isModalOpen}
        handleClose={handleCancel}
        handleConfirm={handleConfirm}
        messageId1="Leave page?"
        messageId2="Unsaved changes will be lost."
        confirmButtonId="confirm_button"
      />
    ),
    setPendingPath,
  };
};

关键修改点说明

  • 新增isConfirming状态:标记用户是否正在执行确认后的导航,此时跳过popstate的拦截逻辑,避免循环触发模态框。
  • 调整返回导航逻辑:当pendingPath为null(浏览器返回按钮触发)时,调用navigate(-2)跳过之前pushState添加的冗余历史记录,直接回到真正的上一页。
  • 延迟重置状态:确保导航操作完成后再恢复拦截状态,防止后续正常的返回操作被误放行。

内容的提问来源于stack exchange,提问作者27Lia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 04:33:20