简体   繁体   English

React Router v5.2 - 使用 createBrowserHistory 和 history.block 阻止路由更改

[英]React Router v5.2 - Blocking route change with createBrowserHistory and history.block

My app has two pages: Step1 and Step2 .我的应用程序有两个页面: Step1Step2 Step1 has a checkbox that blocks navigation if it is checked and a Next button that navigates to Step2 when clicked. Step1有一个复选框,如果选中它会阻止导航,还有一个 Next 按钮,单击它会导航到Step2 Step2 has a Previous button that navigates back to Step1 when clicked. Step2有一个 Previous 按钮,单击该按钮可导航回Step1

Link to demo 演示链接

As per this tutorial , I'm using the block method of the createBrowserHistory object to block route changes if the checkbox in Step1 is checked:根据本教程,如果选中Step1 1 中的复选框,我将使用createBrowserHistory object 的block方法来阻止路由更改:

const unblock = useRef();

  useEffect(() => {
    unblock.current = history.block((tx) => {
      if (block.current.checked) {
        const promptMessage = "Are you sure you want to leave?";

        if (window.confirm(promptMessage)) {
          unblock.current();
          tx.retry();
        }
      } else {
        console.log("tfgx");
        unblock.current();
        tx.retry();
      }
    });
  }, []);

I also had to set the history prop in the low-level <Router> (not <BrowserRouter> ) to the createBrowserHistory object, like so:我还必须将低级<Router> (不是<BrowserRouter> )中的 history 道具设置为createBrowserHistory object,如下所示:

<Router history={createBrowserHistory()}>
...
</Router>

But this prevents the routes from being rendered properly .但这会阻止正确呈现路线 I think this may have something to do with <Switch> not being able to read the location object properly.我认为这可能与<Switch>无法正确读取位置 object 有关。 If I use <BrowserRouter> , the location object looks like this: {pathname: "/step1", ... key: "7sd45"} .如果我使用<BrowserRouter> ,位置 object 如下所示: {pathname: "/step1", ... key: "7sd45"} But when I use <Router={createBrowserHistory()}> , the location object looks like this {action: "PUSH", location: {pathname: "/step1", ... key: "7sd45"}} .但是当我使用<Router={createBrowserHistory()}>时,位置 object 看起来像这样{action: "PUSH", location: {pathname: "/step1", ... key: "7sd45"}} (I'm also getting the warning You cannot change <Router history> .) (我也收到警告You cannot change <Router history> 。)

My desired result is to block navigation if the 'Block navigation' checkbox is checked and unblock it otherwise.如果选中“阻止导航”复选框,我想要的结果是阻止导航,否则取消阻止。 If the location changes when navigation is unblocked, I would like the corresponding route to be rendered correctly.如果在导航畅通时位置发生变化,我希望正确呈现相应的路线。

The section on createBrowserHisory in the React Router v5 docs is sparse and there aren't many examples that make use of it, so I'd be grateful if someone could shed some light on this. React Router v5 文档中关于 createBrowserHisory 的部分很少,并且没有很多使用它的示例,所以如果有人能对此有所说明,我将不胜感激。


EDIT: Passing location.location to <Switch> seems to fix it ( Updated demo ).编辑:location.location传递给<Switch>似乎可以修复它( 更新的演示)。 But if I call useLocation inside Step1 and print the result (line 17-18), I get {pathname: "/step1", ... key: "7sd45"} and not {action: "PUSH", location: {pathname: "/step1", ... key: "7sd45"}} .但是,如果我在Step1中调用useLocation并打印结果(第 17-18 行),我会得到{pathname: "/step1", ... key: "7sd45"}而不是{action: "PUSH", location: {pathname: "/step1", ... key: "7sd45"}} Why is this?为什么是这样?

Also, if the user attempts to go to another location when navigation is blocked, my custom prompt appears as expected ("Are you sure you want to leave" with "OK" and "Cancel" buttons).此外,如果用户在导航被阻止时尝试 go 到另一个位置,我的自定义提示将按预期显示(“您确定要离开吗”以及“确定”和“取消”按钮)。 However, if they dismiss this by clicking Cancel, then the browser's own dialog box appears -但是,如果他们通过单击取消取消此操作,则会出现浏览器自己的对话框 -

In Chrome:在铬:

在此处输入图像描述

In Firefox:在 Firefox 中:

在此处输入图像描述

Is it possible to suppress the browser prompt after my prompt has been dismissed?在我的提示被取消后是否可以禁止浏览器提示?

The router context's history object also has a block function but it works a little differently.路由器上下文的history object 也有一个块 function 但它的工作方式略有不同。 It takes a callback that consumes location and action arguments.它需要一个使用locationaction arguments 的回调。

history.block((location, action) => {...});

Returning false from the callback blocks the navigation transition, returning true allows the transition to go through.从回调返回false会阻止导航转换,返回true允许通过转换到 go。

React.useEffect(() => {
  const unblock = history.block((location, action) => {
    if (checkBlockingCondition) {
      return window.confirm("Navigate Back?");
    }
    return true;
  });

  return () => {
    unblock();
  };
}, []);

Alternatively, react-router-dom suggests using the Prompt component to conditionally block route transitions.或者,react-router-dom 建议使用Prompt组件有条件地阻止路由转换。 Your code is very close to their preventing transitions example.您的代码非常接近他们的防止转换示例。

Updates to your last codesandbox:更新您的最后一个代码框:

  1. Use blocking state versus react ref so the prompt rerenders and reevaluates the condition.使用阻塞 state 与 react ref 相比,以便提示重新呈现并重新评估条件。
  2. Render a Prompt component.渲染一个Prompt组件。
  3. Prevent the default form submit action, ie to prevent the page from reloading.阻止默认的表单提交动作,即阻止页面重新加载。

code代码

import {
  BrowserRouter as Router,
  Prompt, // <-- import Prompt
  Redirect,
  Switch,
  Route,
  useLocation
} from "react-router-dom";

const Step1 = ({ id, history }) => {
  const [isBlocking, setIsBlocking] = useState(false);

  return (
    <form
      id={id}
      onSubmit={(e) => {
        e.preventDefault(); // <-- prevent default form action, i.e. page reload
        history.push("/step2");
      }}
    >
      <label>
        Block navigation
        <input
          type="checkbox"
          onChange={(e) => setIsBlocking(e.target.checked)}
        />
      </label>
      <br />
      <br />
      <button type="submit">Next</button>
      <Prompt
        when={isBlocking} // <-- blocking condition
        message="Are you sure you want to leave?"
     />
    </form>
  );
};

编辑 react-router-v5-2-blocking-route-change-with-createbrowserhistory-and-history

To do this in typescript, you could use a ternary based on your "dirty" condition to show the prompt before navigating to your next route.要在 typescript 中执行此操作,您可以使用基于“脏”条件的三元组来显示提示,然后再导航到下一条路线。

As long as the component this goes in is inside of aa routing context, it will work.只要它进入的组件在路由上下文中,它就会工作。

isChecked is a psuedo-code variable to be replaced by whatever condition you want to evaluate on leaving the page. isChecked是一个伪代码变量,可替换为您要在离开页面时评估的任何条件。 I recommend having it in the dependencies because when the state variable updates, so will the block condition.我建议将它放在依赖项中,因为当 state 变量更新时,块条件也会更新。

  const history = useHistory();

  useEffect(() => {
    const unblock = history.block(
      !isChecked
        ? "You’ve got unsaved changes. Are you sure you want to navigate away from this page?"
        : true
    );

    return function cleanup() {
      unblock();
    };
  }, [isChecked]);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM