在 React Router v6 中,如何在离开页面/路由之前检查表单是否脏

Dev*_*evb 10 reactjs react-router material-ui react-redux

以下是我正在使用的软件包版本。

React version - 16.13.1
react-router-dom version - 6.0.0-beta.0
react-redux version 7.2.0
Material UI version 4.11.0
Run Code Online (Sandbox Code Playgroud)

isDirty当用户试图离开当前页面时,如何/什么是检查表单(已更改)的最佳方法?如果表单isDirty.

我将从内部获取数据useEffect()并使用 redux reducer 来呈现 UI。

我应该声明一个变量来保留原始获取的数据以进行脏检查吗?

这就是我正在做的事情,但它无法正常工作。

组件.js

 useEffect(() => {
    props.fetchUserInfo();
 })
Run Code Online (Sandbox Code Playgroud)

动作.js

export function fetchUserInfo() {
 return (dispatch) => {
     dispatch({type: USER_INITIALSTATE, {Name: 'abc', Age: 20}} 
     )
 }
}
Run Code Online (Sandbox Code Playgroud)

userReducer.js

const initialState = {
  processing: false,
  success: false,
  fail: false,
  Profile: {}
}
let oriState;
let State;
const UserReducer = (state = initialState, action) => {
  if (action.type === USER_INITIALSTATE) {
    oriState = {Profile: action.data};
    State = {...state, Profile: action.data};
    return {...state, Profile: action.data};
  } else if (action.type === OTHERS_ACTION) {
     //update field change
     return {...state, xxx}
  }
}
export const userIsDirty = state => {
  if (oriState && State) {
    return JSON.stringify(oriState.Profile) !== JSON.stringify(State.Profile);
  }
  return false;
};
export default UserReducer;
Run Code Online (Sandbox Code Playgroud)

所以在我的组件中,我调用userIsDirty返回 isDirty 布尔值,但我还没有弄清楚如何捕获离开页面事件并将其用作触发器来进行脏表单检查。

那么如何检测离开当前页面呢?我在 useEffect return(component umount) 上尝试了一些东西,但是 props 没有得到更新的 INITIALSTATE 状态(意味着我会得到 Profile: {}),因为它只运行一次,但是如果我添加 useEffect 可选数组参数,我得到一个无限循环(也许我设置错了?)。

useEffect(() => {
    props.fetchUserInfo();
    return () => {
      console.log(props); //not getting initial state object
    };
  }, []);
Run Code Online (Sandbox Code Playgroud)

我这样做是否正确?我错过了什么?有没有更好/正确的解决方案来实现我想要的?

更新

谢谢@gdh,useBlocker是我想要的。我用它来弹出一个确认对话框。

我将分享我的完整代码和盒子,我相信这可能对将来的某人有所帮助。

使用 useBlocker 显示确认对话框

gdh*_*gdh 15

此答案使用路由器 v6。

  1. 您可以使用usePrompt
  • 当您转到另一条路线时,即在安装时,usePrompt 将显示确认模式/弹出窗口。
  • 当您尝试关闭浏览器时带有消息的通用警报。它在内部处理 beforeunload
usePrompt("Hello from usePrompt -- Are you sure you want to leave?", isBlocking);
Run Code Online (Sandbox Code Playgroud)
  1. 您可以使用useBlocker
  • useBlocker 将在尝试导航时简单地阻止用户,即卸载时
  • 当您尝试关闭浏览器时带有消息的通用警报。它在内部处理 beforeunload
useBlocker(
    () => "Hello from useBlocker -- are you sure you want to leave?",
    isBlocking
  );
Run Code Online (Sandbox Code Playgroud)

1 和 2 的演示

  1. 您也可以使用beforeunload。但是你必须做你自己的逻辑。在此处查看示例

  • 在 React-Router v6.0.0-beta.7 发布后以及当前正式版本 v6.0.2 中,usePrompt 和 useBlocker 已被删除。他们将来会重新审视这些钩子/类似的东西。 (15认同)
  • 好吧,虽然开发人员已经创建了当前版本的 React-router 6,但钩子 `useBlocker` 和 `usePrompt` 被删除了。 (2认同)

acS*_*ter 10

只是为 React Router v6 用户添加一个额外的答案。

从 v6.0.0-beta 开始 - useBlocker 和 usePrompt 已被删除(稍后会添加回来)。

建议如果我们在 v6.0.2(撰写本文时的当前版本)中需要它们,我们应该使用现有代码作为示例。

以下是直接来自这些钩子的 alpha 版本的代码。

因此,要将钩子添加回来,将是以下代码(在应用程序中的任何位置供使用):**我只复制了react-router-dom的代码 - 如果您使用的是本机,那么您需要检查上面的内容另一个usePrompt钩子的链接

/**
 * These hooks re-implement the now removed useBlocker and usePrompt hooks in 'react-router-dom'.
 * Thanks for the idea @piecyk https://github.com/remix-run/react-router/issues/8139#issuecomment-953816315
 * Source: https://github.com/remix-run/react-router/commit/256cad70d3fd4500b1abcfea66f3ee622fb90874#diff-b60f1a2d4276b2a605c05e19816634111de2e8a4186fe9dd7de8e344b65ed4d3L344-L381
 */
import { useContext, useEffect, useCallback } from 'react';
import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom';
/**
 * Blocks all navigation attempts. This is useful for preventing the page from
 * changing until some condition is met, like saving form data.
 *
 * @param  blocker
 * @param  when
 * @see https://reactrouter.com/api/useBlocker
 */
export function useBlocker( blocker, when = true ) {
    const { navigator } = useContext( NavigationContext );

    useEffect( () => {
        if ( ! when ) return;

        const unblock = navigator.block( ( tx ) => {
            const autoUnblockingTx = {
                ...tx,
                retry() {
                    // Automatically unblock the transition so it can play all the way
                    // through before retrying it. TODO: Figure out how to re-enable
                    // this block if the transition is cancelled for some reason.
                    unblock();
                    tx.retry();
                },
            };

            blocker( autoUnblockingTx );
        } );

        return unblock;
    }, [ navigator, blocker, when ] );
}
/**
 * Prompts the user with an Alert before they leave the current screen.
 *
 * @param  message
 * @param  when
 */
export function usePrompt( message, when = true ) {
    const blocker = useCallback(
        ( tx ) => {
            // eslint-disable-next-line no-alert
            if ( window.confirm( message ) ) tx.retry();
        },
        [ message ]
    );

    useBlocker( blocker, when );
}
Run Code Online (Sandbox Code Playgroud)

那么用法将是:

const MyComponent = () => {
    const formIsDirty = true; // Condition to trigger the prompt.
    usePrompt( 'Leave screen?', formIsDirty );
    return (
        <div>Hello world</div> 
    );
};
Run Code Online (Sandbox Code Playgroud)