当用户正在进行工作流程,如填写一个重要的表单时,您可能希望阻止他们离开当前页面。
本示例将展示
添加一个带表单的路由,本例中我们将使用一个“联系人”路由
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>
);
}
为了跟踪表单的 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>
);
}
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
}
虽然现在会阻塞导航,但用户没有办法确认它。
这里使用了一个简单的 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()
将清除阻塞器并让他们留在当前页面。
如果用户既不点击“离开”也不点击“留在这里”,然后提交了表单,阻塞器仍然会处于活动状态。让我们在操作解析时使用一个 effect 来重置阻塞器。
useEffect(() => {
if (fetcher.data?.ok) {
if (blocker.state === "blocked") {
blocker.reset();
}
}
}, [fetcher.data]);
虽然与导航阻塞无关,但让我们在操作解析时使用一个 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]);
在这种情况下,用户流程是