关于前端:离开页面前如何防止表单数据丢失

1次阅读

共计 8025 个字符,预计需要花费 21 分钟才能阅读完成。

本文首发于微信公众号:大迁世界, 我的微信:qq449245884,我会第一工夫和你分享前端行业趋势,学习路径等等。
更多开源作品请看 GitHub https://github.com/qq449245884/xiaozhi,蕴含一线大厂面试残缺考点、材料以及我的系列文章。

快来收费体验 ChatGpt plus 版本的,咱们出的钱
体验地址:https://chat.waixingyun.cn
能够退出网站底部技术群,一起找 bug.

本文介绍了如何实现一个 FormPrompt 组件,在用户尝试来到具备未保留更改的页面时收回正告。文章探讨了如何应用纯 JavaScript 和 beforeunload 事件处理这类状况,以及应用 React Router v5 中的 Prompt 组件和 useBeforeUnload 以及 unstable 等 React 特定解决方案。向用户增加一个确认对话框,询问他们在具备未保留表单更改的状况下是否确认重定向是一种良好的用户体验实际。通过显示此提醒,用户将意识到他们有未保留的更改,并容许在持续重定向之前保留或抛弃它们的工作。

上面是注释~

在明天的数字化环境中,为波及表单提交的 Web 应用程序提供最佳用户体验十分重要。用户常见的一个懊恼起源是因为意外来到页面而失落未保留的更改。

本文将演示如何实现一个 FormPrompt 组件,当用户尝试来到具备未保留更改的页面时,会收回警报,从而无效地进步整体用户体验。咱们将探讨如何应用纯 JavaScript 解决此类情况,应用 React Router v5 中的 Prompt 组件以及在 React Router v6 中应用 useBeforeUnloadunstable_useBlocker 钩子的特定解决方案。

应用程序的最终版本能够在 CodeSandbox 上进行测试,代码可在 GitHub 上取得。

应用 beforeunload 事件检测页面来到

咱们创立 FormPrompt 组件,在其中增加 beforeunload 事件的监听器。此事件将在用户来到页面之前触发。通过在事件上调用 preventDefault 办法,咱们能够触发浏览器的确认对话框。仅当表单具备未保留的更改(由 hasUnsavedChanges 属性批示)时,才会激活此对话框。

// FormPrompt.js

import {useEffect} from "react";

export const FormPrompt = ({hasUnsavedChanges}) => {useEffect(() => {const onBeforeUnload = (e) => {if (hasUnsavedChanges) {e.preventDefault();
        e.returnValue = "";
      }
    };
    window.addEventListener("beforeunload", onBeforeUnload);
    return () => {window.removeEventListener("beforeunload", onBeforeUnload);
    };
  }, [hasUnsavedChanges]);
};

作为示例,咱们将在表单的 Contact 步骤中应用此组件:

// Steps/Contact.js

import {forwardRef} from "react";
import {useForm} from "react-hook-form";
import {useAppState} from "../state";
import {Button, Field, Form, Input} from "../Forms";
import {FormPrompt} from "../FormPrompt";

export const Contact = forwardRef((props, ref) => {const [state, setState] = useAppState();
  const {
    handleSubmit,
    register,
    formState: {isDirty},
  } = useForm({
    defaultValues: state,
    mode: "onSubmit",
  });

  const saveData = (data) => {setState({ ...state, ...data});
  };

  return (<Form onSubmit={handleSubmit(saveData)} nextStep={"/education"}>
      <FormPrompt hasUnsavedChanges={isDirty} />
      <fieldset>
        <legend>Contact</legend>
        <Field label="First name">
          <Input {...register("firstName")} id="first-name" />
        </Field>
        <Field label="Last name">
          <Input {...register("lastName")} id="last-name" />
        </Field>
        <Field label="Email">
          <Input {...register("email")} type="email" id="email" />
        </Field>
        <Field label="Password">
          <Input {...register("password")} type="password" id="password" />
        </Field>
        <Button ref={ref}>Next {">"}</Button>
      </fieldset>
    </Form>
  );
});

当在表单字段中输出数据并在保留更改之前尝试从新加载页面或导航到内部 URL 时,浏览器将显示确认对话框。

应用 React Router 5 避免页面导航

这个组件曾经足够好用于咱们的应用程序,因为它的所有页面都是表单的一部分。然而,在理论状况下,这并不总是如此。为了使咱们的示例更具代表性,咱们增加一个名为 Home 的新路由,它将重定向到表单之外。Home 组件很简略,只显示一个主页问候语。

// Home.js

export const Home = () => {return <div>Welcome to the home page!</div>;};

咱们还须要对 App 组件进行一些调整,以适应这条新路由。

// App.js

import {useRef} from "react";
import {
  BrowserRouter as Router,
  Routes,
  Route,
  NavLink,
} from "react-router-dom";
import {AppProvider} from "./state";
import {Contact} from "./Steps/Contact";
import {Education} from "./Steps/Education";
import {About} from "./Steps/About";
import {Confirm} from "./Steps/Confirm";
import {Stepper} from "./Steps/Stepper";
import {Home} from "./Home";

export const App = () => {const buttonRef = useRef();

  const onStepChange = () => {buttonRef.current?.click();
  };

  return (
    <div className="App">
      <AppProvider>
        <Router>
          <div className="nav-wrapper">
            <NavLink to={"/"}>Home</NavLink>
            <Stepper onStepChange={onStepChange} />
          </div>
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/contact" element={<Contact ref={buttonRef} />} />
            <Route path="/education" element={<Education ref={buttonRef} />} />
            <Route path="/about" element={<About ref={buttonRef} />} />
            <Route path="/confirm" element={<Confirm />} />
          </Routes>
        </Router>
      </AppProvider>
    </div>
  );
};

咱们能够看到当咱们在表格中输出信息并导航到主页时,输出的数据不会被保留,也不会呈现任何确认对话框。这是因为导航由 React Router 解决,不会触发 beforeunload 事件,使浏览器 API 在这种状况下有效。侥幸的是,React Router v5 提供了 Prompt 组件,以在来到未保留更改的页面之前正告用户。该组件承受两个propswhenmessagewhen 属性是一个布尔值,用于确定是否应该显示提醒,而 message 属性示意向用户显示的文本。

应用 Prompt 时,导航到主页路由时行为正确,然而当用户输出表单数据并进入下一步时,确认对话框也会呈现。这是不心愿的,因为咱们在导航到下一步时保留表单数据。

为了解决这个问题,咱们须要验证下一个 URL 是否是表单步骤之一,而后再查看未保留的更改。能够应用 message 属性来实现这一点,它也能够是一个函数。该函数的第一个参数是下一个地位。如果函数返回 true,则容许转换到下一个 URL;否则,它能够返回一个字符串来显示提醒。

// FormPrompt.js

import {useEffect} from "react";
import {Prompt} from "react-router-dom";

const stepLinks = ["/contact", "/education", "/about", "/confirm"];

export const FormPrompt = ({hasUnsavedChanges}) => {useEffect(() => {const onBeforeUnload = (e) => {if (hasUnsavedChanges) {e.preventDefault();
        e.returnValue = "";
      }
    };
    window.addEventListener("beforeunload", onBeforeUnload);

    return () => {window.removeEventListener("beforeunload", onBeforeUnload);
    };
  }, [hasUnsavedChanges]);

  const onLocationChange = (location) => {if (stepLinks.includes(location.pathname)) {return true;}
    return "You have unsaved changes, are you sure you want to leave?";
  };

  return <Prompt when={hasUnsavedChanges} message={onLocationChange} />;
};

通过这些更改,咱们能够平安地在表单步骤之间导航,并在尝试来到未保留更改的表单时收到正告。

应用 React Router 6 避免页面导航

件已被移除,而 unstable_usePrompt 钩子在 6.7.0 版本中被增加。正如其名称所示,该钩子的实现可能会发生变化,尚未记录文档。然而,它应该实用于咱们的应用状况。

咱们能够应用这个钩子来复制版本 5 中 Prompt 组件的行为,但首先,咱们须要调整咱们的 App 组件以应用新的数据路由器,因为它们是 unstable_usePrompt 钩子工作所必须的。

// App.js

import {useRef} from "react";
import {createBrowserRouter, RouterProvider, Outlet} from "react-router-dom";
import {AppProvider} from "./state";
import {Contact} from "./Steps/Contact";
import {Education} from "./Steps/Education";
import {About} from "./Steps/About";
import {Confirm} from "./Steps/Confirm";
import {Stepper} from "./Steps/Stepper";
import {Home} from "./Home";

export const App = () => {const buttonRef = useRef();

  const onStepChange = () => {buttonRef.current?.click();
  };

  const router = createBrowserRouter([
    {
      element: (
        <>
          <Stepper onStepChange={onStepChange} />
          <Outlet />
        </>
      ),
      children: [
        {
          path: "/",
          element: <Home />,
        },
        {
          path: "/contact",
          element: <Contact ref={buttonRef} />,
        },
        {path: "/education", element: <Education ref={buttonRef} /> },
        {path: "/about", element: <About ref={buttonRef} /> },
        {path: "/confirm", element: <Confirm />},
      ],
    },
  ]);

  return (
    <div className="App">
      <AppProvider>
        <RouterProvider router={router} />
      </AppProvider>
    </div>
  );
};

咱们应用 createBrowserRouter 函数来创立路由器。请留神,Stepper 没有独自的门路,所有其余路由都是它的子路由。它作为布局组件,在每个页面上出现。每个页面的内容显示在非凡的 Outlet 组件的地位。为了简化 App 逻辑,咱们还将主页导航链接挪动到 Stepper 中。

设置实现后,咱们当初能够实现重定向阻止性能。咱们首先通过在 FormPrompt 中应用在 6.6 版本中引入的 useBeforeUnload 钩子来替换 onbeforeunload 逻辑。

// FormPrompt.js

import {useEffect, useCallback, useRef} from "react";
import {useBeforeUnload} from "react-router-dom";

const stepLinks = ["/contact", "/education", "/about", "/confirm"];

export const FormPrompt = ({hasUnsavedChanges}) => {
  useBeforeUnload(
    useCallback((event) => {if (hasUnsavedChanges) {event.preventDefault();
          event.returnValue = "";
        }
      },
      [hasUnsavedChanges]
    ),
    {capture: true}
  );

  return null;
};

这个扭转简化了咱们组件的逻辑。当初,咱们能够增加一个自定义的 usePrompt 钩子,并像版本 5 中的 Prompt 组件一样应用它。

// FormPrompt.js

import {useEffect, useCallback, useRef} from "react";
import {
  useBeforeUnload,
  unstable_useBlocker as useBlocker,
} from "react-router-dom";

const stepLinks = ["/contact", "/education", "/about", "/confirm"];

export const FormPrompt = ({hasUnsavedChanges}) => {
  const onLocationChange = useCallback(({ nextLocation}) => {if (!stepLinks.includes(nextLocation.pathname) && hasUnsavedChanges) {
        return !window.confirm("You have unsaved changes, are you sure you want to leave?");
      }
      return false;
    },
    [hasUnsavedChanges]
  );

  usePrompt(onLocationChange, hasUnsavedChanges);
  useBeforeUnload(
    useCallback((event) => {if (hasUnsavedChanges) {event.preventDefault();
          event.returnValue = "";
        }
      },
      [hasUnsavedChanges]
    ),
    {capture: true}
  );

  return null;
};

function usePrompt(onLocationChange, hasUnsavedChanges) {const blocker = useBlocker(hasUnsavedChanges ? onLocationChange : false);
  const prevState = useRef(blocker.state);

  useEffect(() => {if (blocker.state === "blocked") {blocker.reset();
    }
    prevState.current = blocker.state;
  }, [blocker]);
}

useBlocker 钩子承受布尔值或阻止函数作为其参数,相似于 Prompt 组件中的 message 属性。该函数的一个参数是下一个地位,咱们应用它来确定用户是否正在来到咱们的表单。如果是这种状况,咱们利用浏览器的 window.confirm 办法显示一个对话框,询问用户确认重定向或勾销它。最初,咱们在 usePrompt 钩子中形象出阻止逻辑并治理阻止器的状态。

咱们能够通过导航到分割步骤,填写一些字段并单击主页导航项来测试 FormPrompt 是否按预期工作。咱们会看到一个确认对话框,询问咱们是否要来到该页面。

总结

总之,为未保留的表单更改实现确认对话框是加强用户体验的重要实际。本文演示了如何创立一个 FormPrompt 组件,当用户尝试来到具备未保留更改的页面时,该组件会向用户收回正告。咱们探讨了如何应用纯 JavaScript 解决这种状况,应用 beforeunload 事件以及在 React 中应用 React Router v5 中的 Prompt 组件和 React Router v6 中的 useBeforeUnload 和 unstable_useBlocker 钩子。通过将此性能合并到您的表单中,你能够帮忙用户防止失去未保留的工作而感到丧气。

代码部署后可能存在的 BUG 没法实时晓得,预先为了解决这些 BUG,花了大量的工夫进行 log 调试,这边顺便给大家举荐一个好用的 BUG 监控工具 Fundebug。

原文:https://claritydev.net/blog/display-warning-for-unsaved-form-…

交换

有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

正文完
 0