useBlocker 防止页面跳转导致的表单丢失

关于useBlocker

The hook allows you to prevent the user from navigating away from the
current location, and present them with a custom UI to allow them to
confirm the navigation

在react-router的v6版本之前,我们会使用<Prompt />组件来拦截路由的跳转。但最近新开的项目中,发现了没有<Prompt />组件了,所以在开发的版本中,使用了prompt的替代品----useBlocker

但是在v6的早期版本中,如6.7等版本中,该钩子还是属于unsafe的状态,所以需要用到 unstable_useBlocker 来开发,而不是 useBlocker 。吐槽一句,现在的 react-router 让人很懵逼,每次在找文档的过程中,总是看到一大堆的状态管理,整的我以为自己在看react-redux了。。。

useBlocker的使用

吐槽完了react-router的文档之后,是时候进入正题了。

下面会用一个简单的例子展示下如何使用useBlocker~

老规矩,当前的示例中,react-router的版本如下:

"react-router-dom": "^6.16.0",

因为是项目所使用的版本我也不会去做出更改,所以下面的示例依旧使用的是 unstable_useBlocker

首先,我们会定义一个组件,由于起到的作用类似之前的Prompt,所以我将之命名为usePrompt。

import { useEffect, useRef } from 'react';
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
import { BlockerFunction } from '@remix-run/router/router';
​
export function usePrompt(onLocationChange: BlockerFunction, hasUnsavedChanges: boolean) {
  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 钩子中抽象出阻止逻辑并管理阻止器的状态。

将其封装为一个钩子后…吐槽一句…我现在也不得不用钩子了…
在我们的页面中,我们就可以使用其来做跳转的判定了。

​useBlocker 的图像结果

从组件的封装角度来说,由于这一块主要是做表单提交的校验,所以下面会创建一个名字叫 FormPrompt 的组件。代码如下:

import React, { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useDisclosure } from '@nextui-org/react';
import { BlockerFunction } from '@remix-run/router/router';
import { Location } from '@remix-run/router/dist/history';
import CancelPlanCreationModal from 'pages/Plans/CancelPlanCreationModal';
import { usePrompt } from 'components/FormPrompt/usePrompt';
​
interface Props {
  /**
   * The `hasUnsavedChanges` prop indicate whether the form has unsaved changes.
   */
  hasUnsavedChanges: boolean;
}
​
const FormPrompt: React.FC<Props> = ({ hasUnsavedChanges }) => {
  const navigate = useNavigate();
  const {
    isOpen: isLeaveConfirmModalOpen,
    onOpen: handleOpenLeaveConfirmModal,
    onOpenChange: handleLeaveConfirmModalOpenChange
  } = useDisclosure();
  const [lastLocation, setLastLocation] = useState<Location | null>(null);
  const [confirmedNavigation, setConfirmedNavigation] = useState(false);
​
  const handleConfirmNavigation = useCallback(() => {
    // confirm navigation after 350ms to allow the modal to finish closing animation before redirecting
    // in order to fix the issue: Failed to execute 'createTreeWalker' on 'Document': parameter 1 is not of type 'Node'
    setTimeout(() => {
      setConfirmedNavigation(true);
    }, 350);
  }, []);
​
  const handleCancelNavigation = useCallback(() => {
    setLastLocation(null);
  }, []);
​
  const onLocationChange: BlockerFunction = useCallback(
    ({ nextLocation, currentLocation }) => {
      if (!confirmedNavigation && nextLocation.pathname !== currentLocation.pathname && hasUnsavedChanges) {
        setLastLocation(nextLocation);
        handleOpenLeaveConfirmModal();
        return true;
      }
      return false;
    },
    [confirmedNavigation, hasUnsavedChanges, handleOpenLeaveConfirmModal]
  );usePrompt(onLocationChange, hasUnsavedChanges);useEffect(() => {
    if (confirmedNavigation && lastLocation) {
      navigate(lastLocation.pathname);
      setConfirmedNavigation(false);
    }
  }, [confirmedNavigation, lastLocation, navigate]);return (
    <CancelPlanCreationModal
      isOpen={isLeaveConfirmModalOpen}
      onOpenChange={handleLeaveConfirmModalOpenChange}
      onLeave={handleConfirmNavigation}
      onCancel={handleCancelNavigation}
      title="Leave plan creation?"
    />
  );
};
​
export default FormPrompt;


先要明确的是,该组件的作用是做路由跳转的判断,类似Vue的离开路由,所以我们需要用到useNavigate作为跳转的操作,而页面是否需要提示,则我们需要自己传入到这个组件中。

理解了这些基本东西之后,首先我们可以看到我们会判断传进来的hasUnsavedChanges参数,当其改变的时候,我们需要去判断是否打开弹窗。

  const onLocationChange: BlockerFunction = useCallback(
    ({ nextLocation, currentLocation }) => {
      if (!confirmedNavigation && nextLocation.pathname !== currentLocation.pathname && hasUnsavedChanges) {
        setLastLocation(nextLocation);
        handleOpenLeaveConfirmModal();
        return true;
      }
      return false;
    },
    [confirmedNavigation, hasUnsavedChanges, handleOpenLeaveConfirmModal]
  );
简化版的代码如下

 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]
  );

接着,我们将其传入到我们定义好的hook中

  usePrompt(onLocationChange, hasUnsavedChanges);

最后再将想要显示的组件显示出来

  return (
    <CancelPlanCreationModal
      isOpen={isLeaveConfirmModalOpen}
      onOpenChange={handleLeaveConfirmModalOpenChange}
      onLeave={handleConfirmNavigation}
      onCancel={handleCancelNavigation}
      title="Leave plan creation?"
    />

基本流程如此,为了简单点,下面会贴上简单版的代码方便参考

function Home() {
  const [value, setValue] = useState("");
  const blocker = useBlocker(!!value);
  
  useEffect(() => {
    if (blocker.state === "blocked") {
      Modal.confirm({
        message: "确认离开吗",
        onOk: () => {
          blocker.proceed?.();
        },
        onCancel: () => {
          blocker.reset?.();
        },
      });
    }
  }, [blocker]);return (
    <div>
      <Link to="/about" />
      <input value={value} onChange={(e) => setValue(e.target.value)} />
    </div>
  );
}

在上述代码中,直接简单的判断输入框来决定是否跳转,只是项目可能没那么简单,要注意封装噢…不然挨骂了别找我…

不足点

毕竟useBlocker也是根据react-router来干活的,所以当你用window.location来跳转的时候,是无法做到监听的​。

在这里插入图片描述
公众号文章链接~求关注

创作不易,感谢观看,希望能帮到您

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/516849.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

9、鸿蒙学习-开发及引用静态共享包(API 9)

HAR&#xff08;Harmony Archive&#xff09;是静态共享包&#xff0c;可以包含代码、C库、资源和配置文件。通过HAR可以实现多个模块或多个工程共享ArkUI组件、资源等相关代码。HAR不同于HAP&#xff0c;不能独立安装运行在设备上&#xff0c;只能作为应用模块的依赖项被引用。…

【C++ STL排序容器】set 集合

文章目录 【 1. 基本原理 】【 2. set 的定义 】2.1 调用默认构造函数&#xff0c;创建空的 set 容器2.2 在创建 set 容器的同时&#xff0c;对其进行初始化2.3 拷贝构造的方式创建2.4 取已有 set 容器中的部分元素&#xff0c;来初始化新 set 容器2.5 修改排序规则的方式创建 …

1.Spring Boot框架整合

Spring Boot项目创建&#xff08;约定大于配置&#xff09; 2.1.3.RELEASE版本示例 idea创建 从官网下载&#xff08;https://start.spring.io/&#xff09;单元测试默认依赖不对时&#xff0c;直接删除即可 Web支持&#xff08;SpringMVC&#xff09; <dependency>&…

Linux操作系统之防火墙

目录 一、防火墙 1、防火墙的类别 2、安装iptables(四表五链&#xff09; ​​​​​​​一、防火墙 1、防火墙的类别 安全产品 杀毒 针对病毒&#xff0c;特征篡改系统中文件杀毒软件针对处理病毒程序 防火墙 针对木马&#xff0c;特征系统窃密 防火墙针对处理木马 防火墙…

基于springboot实现美容院管理系统项目【项目源码+论文说明】

基于springboot实现美容院管理系统演示 摘要 如今的信息时代&#xff0c;对信息的共享性&#xff0c;信息的流通性有着较高要求&#xff0c;因此传统管理方式就不适合。为了让美容院信息的管理模式进行升级&#xff0c;也为了更好的维护美容院信息&#xff0c;美容院管理系统的…

关于第十二届蓝桥杯时间显示题中包和模块的使用解释

题目信息&#xff1a; 解题代码&#xff1a; from datetime import datetime, timedelta # 定义起始时间&#xff0c;即 Unix 时间戳的零点&#xff08;1970年1月1日&#xff09; start datetime(year1970, month1, day1) # 定义时间间隔&#xff0c;这里以毫秒为单位 dela …

用3点结构数列构造4点结构数列

在一个行和列可以自由变换的平面上&#xff0c;3个点只有6种相对位置关系 现在有一个3点的数列就按照1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6的顺序排列&#xff0c;让这个数列按照431&#xff0c;34-1的方式递推&#xff0c;直到稳定。 前4次迭…

混合专家(MoE)模型示例

文心一言 混合专家&#xff08;Mixture of Experts&#xff0c;MoE&#xff09;模型是一种机器学习架构&#xff0c;它结合了多个专家模型&#xff08;或子模型&#xff09;以处理不同的输入数据或任务。每个专家模型在其特定领域内表现出色&#xff0c;而MoE模型则负责根据输…

软件架构复用

1.软件架构复用的定义及分类 软件产品线是指一组软件密集型系统&#xff0c;它们共享一个公共的、可管理的特性集&#xff0c;满足某个特定市场或任务的具体需要&#xff0c;是以规定的方式用公共的核心资产集成开发出来的。即围绕核心资产库进行管理、复用、集成新的系统。核心…

.locked勒索病毒的最新威胁:如何恢复您的数据?

导言&#xff1a; 在当今日益数字化的世界中&#xff0c;网络安全问题日益凸显&#xff0c;其中勒索病毒便是一个不容忽视的威胁。近年来&#xff0c;.locked勒索病毒逐渐崭露头角&#xff0c;其恶意行为给广大用户带来了极大的困扰。本文91数据恢复将简要介绍.locked勒索病毒…

DWARF简析

sevaa/dwex: DWARF Explorer - a GUI utility for navigating the DWARF debug information (github.com)eliben/pyelftools: Parsing ELF and DWARF in Python (github.com)8 调试信息标准: DWARF GitBook (hitzhangjie.pro) 1.需求 通过elf获取到原文件中的相关数据定义&am…

LeetCode-热题100:2. 两数相加

题目描述 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都…

金融贷款批准预测项目

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 在金融服务行业&#xff0c;贷款审批是一项关键任务&#xff0c;它不仅关系到资金的安全&#xff0c;还直接影响到金融机构的运营效率和风险管理…

【Java笔记】多线程0:JVM线程是用户态还是内核态?Java 线程与OS线程的联系

文章目录 JVM线程是用户态线程还是内核态线程什么是用户态线程与内核态线程绿色线程绿色线程的缺点 线程映射稍微回顾下线程映射模型JVM线程映射 线程状态操作系统的线程状态JVM的线程状态JVM线程与OS线程的状态关系 Reference 今天复盘一下Java中&#xff0c;JVM线程与实际操作…

面试算法-140-接雨水

题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3,2…

Java web第一次作业

1.学会用记事本编写jsp文件&#xff0c;并放进tomcat的相关目录下&#xff0c;运行。 源代码&#xff1a; <% page contentType"text/html;charsetUTF-8" language"java" %> <html> <head> <title>我的第一个JSP页面</ti…

在深度学习模型中引入先验

当面对复杂问题的时候&#xff0c;在深度学习模型提取特征的过程中完全抛弃知识是非常不明智的策略。虽然有很多研究者在深度网络处理数据之前&#xff0c;利用具有某种知识的模型驱动方法对数据进行预处理&#xff0c;但是这种方法没有进行实质性地改造深度网络&#xff0c;且…

组合总和 II-java

题目描述: 给定一个候选人编号的集合 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次 。 注意&#xff1a;解集不能包含重复的组合。 解题思想: 回溯法 剪枝 : …

PWM技术的应用

目录 PWM技术简介 PWM重要参数 PWM实现呼吸灯 脉宽调制波形 PWM案例 电路图 keil文件 直流电机 直流电机的控制 直流电机的驱动芯片L293D L293D引脚图 L293D功能表 直流电机案例 电路图 keil文件 步进电机 步进电机特点 步进电机驱动芯片L298 L298引脚图 L…

【Canvas与艺术】绘制黑白纹章,内嵌陶渊明南山诗

【效果图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>用Canvas绘制黑白纹章</title><style type"text/css…