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

React Router v3实现返回/刷新未保存数据确认弹窗方案咨询

Solution for Unsaved Form Navigation Block in React Router v3

Got it, let's work through this problem since you're stuck on React Router v3 and can't use the v4+ <Prompt> component. First, let's break down why your existing approaches had issues, then implement a reliable solution that covers all scenarios: browser back/forward, refresh, and client-side route jumps.


Why Your Initial Approaches Failed

  1. setRouteLeaveHook Issue:
    When you returned a string from the hook, React Router v3 triggers the browser's default confirm dialog—but when the user clicks "Cancel", the router doesn't properly revert the URL change (this is a known quirk in v3). Also, you weren't cleaning up the hook when isDirty became false, which could lead to false positives.

  2. beforeunload + popstate Issue:

    • beforeunload works for refreshes/tab closes, but it doesn't trigger for client-side route changes (since those don't reload the page).
    • popstate fires after the navigation has already happened, so e.preventDefault() does nothing to block it.

The Reliable Combined Solution

We'll combine setRouteLeaveHook (for client-side route navigation) with beforeunload (for refreshes/tab closes), and fix the URL mismatch issue with proper state management.

Step 1: Full Component Implementation

import React from 'react';
import { withRouter } from 'react-router';

class UnsavedForm extends React.Component {
  state = {
    isDirty: false,
    // Track if user confirmed leaving to avoid looped prompts
    confirmedLeave: false
  };

  componentDidMount() {
    this.setupLeaveHook();
    window.addEventListener('beforeunload', this.handleBeforeUnload);
  }

  componentDidUpdate(prevProps, prevState) {
    // Re-setup hook only when isDirty state changes
    if (prevState.isDirty !== this.state.isDirty) {
      this.setupLeaveHook();
    }
  }

  componentWillUnmount() {
    // Clean up hook and event listeners to prevent memory leaks
    if (this.leaveHook) this.leaveHook();
    window.removeEventListener('beforeunload', this.handleBeforeUnload);
  }

  setupLeaveHook() {
    const { router, route } = this.props;

    // Remove existing hook first to avoid duplicate bindings
    if (this.leaveHook) this.leaveHook();

    if (this.state.isDirty && !this.state.confirmedLeave) {
      this.leaveHook = router.setRouteLeaveHook(route, (nextLocation) => {
        const shouldLeave = window.confirm('Unsaved data will be lost. Are you sure you want to leave?');
        
        if (shouldLeave) {
          // Mark as confirmed so we don't prompt again during this navigation
          this.setState({ confirmedLeave: true });
          // Manually trigger the navigation since we're blocking the default
          router.push(nextLocation.pathname);
        }

        // Return false to block the default router navigation
        return false;
      });
    }
  }

  handleBeforeUnload = (e) => {
    if (this.state.isDirty) {
      const warningMsg = 'Unsaved data will be lost. Are you sure you want to leave?';
      e.preventDefault();
      e.returnValue = warningMsg; // Required for some browsers
      return warningMsg;
    }
  };

  // Update isDirty when form changes, and reset confirmedLeave state
  handleFormChange = () => {
    this.setState({ isDirty: true, confirmedLeave: false });
  };

  // Reset isDirty after saving
  handleSave = () => {
    // Add your form save logic here
    this.setState({ isDirty: false });
  };

  render() {
    return (
      <form>
        <input 
          type="text" 
          onChange={this.handleFormChange} 
          placeholder="Enter some text..."
        />
        <button onClick={this.handleSave} disabled={!this.state.isDirty}>
          Save Changes
        </button>
      </form>
    );
  }
}

// Wrap with withRouter to access router props
export default withRouter(UnsavedForm);

Step 2: Key Fixes & Explanations

  • Cleanup Hooks/Listeners: We remove the route leave hook and beforeunload listener when the component unmounts to prevent memory leaks and false prompts in other components.
  • Controlled Navigation: Instead of relying on the router's default behavior, we manually trigger navigation only when the user confirms leaving—this fixes the URL mismatch issue you saw earlier.
  • State Tracking: The confirmedLeave flag prevents the prompt from re-firing during the same navigation flow.
  • Full Coverage:
    • setRouteLeaveHook handles browser back/forward buttons and client-side route links.
    • beforeunload handles page refreshes and tab/window closes.

内容的提问来源于stack exchange,提问作者Sang Yun Park

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:39:04