导航阻塞
本页内容

导航阻塞



当用户正在进行工作流程,如填写一个重要的表单时,您可能希望阻止他们离开当前页面。

本示例将展示

  • 设置一个带表单的路由,并使用 fetcher 调用其 action
  • 当表单为 dirty 状态时阻塞导航
  • 当用户尝试离开页面时显示确认信息

1. 设置一个带表单的路由

添加一个带表单的路由,本例中我们将使用一个“联系人”路由

import {
  type RouteConfig,
  index,
  route,
} from "@react-router/dev/routes";

export default [
  index("routes/home.tsx"),
  route("contact", "routes/contact.tsx"),
] satisfies RouteConfig;

将表单添加到联系人路由模块

import { useFetcher } from "react-router";
import type { Route } from "./+types/contact";

export async function action({
  request,
}: Route.ActionArgs) {
  let formData = await request.formData();
  let email = formData.get("email");
  let message = formData.get("message");
  console.log(email, message);
  return { ok: true };
}

export default function Contact() {
  let fetcher = useFetcher();

  return (
    <fetcher.Form method="post">
      <p>
        <label>
          Email: <input name="email" type="email" />
        </label>
      </p>
      <p>
        <textarea name="message" />
      </p>
      <p>
        <button type="submit">
          {fetcher.state === "idle" ? "Send" : "Sending..."}
        </button>
      </p>
    </fetcher.Form>
  );
}

2. 添加 dirty 状态和 onChange 处理程序

为了跟踪表单的 dirty 状态,我们将使用一个布尔值和一个快速的表单 onChange 处理程序。您可能希望以不同的方式跟踪 dirty 状态,但这种方式适用于本指南。

export default function Contact() {
  let [isDirty, setIsDirty] = useState(false);
  let fetcher = useFetcher();

  return (
    <fetcher.Form
      method="post"
      onChange={(event) => {
        let email = event.currentTarget.email.value;
        let message = event.currentTarget.message.value;
        setIsDirty(Boolean(email || message));
      }}
    >
      {/* existing code */}
    </fetcher.Form>
  );
}

3. 当表单为 dirty 状态时阻塞导航

import { useBlocker } from "react-router";

export default function Contact() {
  let [isDirty, setIsDirty] = useState(false);
  let fetcher = useFetcher();
  let blocker = useBlocker(
    useCallback(() => isDirty, [isDirty]),
  );

  // ... existing code
}

虽然现在会阻塞导航,但用户没有办法确认它。

4. 显示确认 UI

这里使用了一个简单的 div,但您可能希望使用一个模态对话框。

export default function Contact() {
  let [isDirty, setIsDirty] = useState(false);
  let fetcher = useFetcher();
  let blocker = useBlocker(
    useCallback(() => isDirty, [isDirty]),
  );

  return (
    <fetcher.Form
      method="post"
      onChange={(event) => {
        let email = event.currentTarget.email.value;
        let message = event.currentTarget.message.value;
        setIsDirty(Boolean(email || message));
      }}
    >
      {/* existing code */}

      {blocker.state === "blocked" && (
        <div>
          <p>Wait! You didn't send the message yet:</p>
          <p>
            <button
              type="button"
              onClick={() => blocker.proceed()}
            >
              Leave
            </button>{" "}
            <button
              type="button"
              onClick={() => blocker.reset()}
            >
              Stay here
            </button>
          </p>
        </div>
      )}
    </fetcher.Form>
  );
}

如果用户点击“离开”,那么 blocker.proceed() 将会继续进行导航。如果他们点击“留在这里”,那么 blocker.reset() 将清除阻塞器并让他们留在当前页面。

5. 当操作解析后重置阻塞器

如果用户既不点击“离开”也不点击“留在这里”,然后提交了表单,阻塞器仍然会处于活动状态。让我们在操作解析时使用一个 effect 来重置阻塞器。

useEffect(() => {
  if (fetcher.data?.ok) {
    if (blocker.state === "blocked") {
      blocker.reset();
    }
  }
}, [fetcher.data]);

6. 当操作解析后清空表单

虽然与导航阻塞无关,但让我们在操作解析时使用一个 ref 来清空表单。

let formRef = useRef<HTMLFormElement>(null);

// put it on the form
<fetcher.Form
  ref={formRef}
  method="post"
  onChange={(event) => {
    // ... existing code
  }}
>
  {/* existing code */}
</fetcher.Form>;
useEffect(() => {
  if (fetcher.data?.ok) {
    // clear the form in the effect
    formRef.current?.reset();
    if (blocker.state === "blocked") {
      blocker.reset();
    }
  }
}, [fetcher.data]);

或者,如果一个导航当前被阻塞,可以不重置阻塞器,而是继续进行被阻塞的导航。

useEffect(() => {
  if (fetcher.data?.ok) {
    if (blocker.state === "blocked") {
      // proceed with the blocked navigation
      blocker.proceed();
    } else {
      formRef.current?.reset();
    }
  }
}, [fetcher.data]);

在这种情况下,用户流程是

  • 用户填写表单
  • 用户忘记点击“发送”,而是点击了一个链接
  • 导航被阻塞,并显示确认消息
  • 用户没有点击“离开”或“留在这里”,而是提交了表单
  • 用户被带到请求的页面
文档和示例 CC 4.0
编辑