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 (<CancelPlanCreationModalisOpen={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 (<CancelPlanCreationModalisOpen={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来跳转的时候,是无法做到监听的​。

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【JAVASE】学习面向对象程序三大特性之一( 封装)

✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609;\n &#x1f34e;个人主页&#xff1a;再无B&#xff5e;U&#xff5e;G-CSDN博客 目标&#xff1a; 1.包的使用 2.static关键字的使用 3.代码…

随机生成Long全范围数

随机生成Long全范围数 前言实现思路主要代码分区随机生成过程案例&#xff1a;随机生成100个数 朴素的比较总结 前言 使用自带的Random.nextLong()函数生成Long型的长整数&#xff0c;范围比较小&#xff0c;如下图。100个随机数没看见10以内的数字。所以考虑实现随机化生成大…

golang语言系列:Scrum、Kanban等敏捷管理策略

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 golang语言系列 文章&#xff0c;主要对编程通用技能 Scrum、Kanban等敏捷管理策略 进行学习 1.什么是敏捷开发 敏捷是一个描述软件开发方法的术语&#xff0c;它强调增量交付、团队协作、持续规划和持续学习。…

代码随想录算法训练营第二十九天(回溯5)|491. 非递减子序列、46. 全排列、47. 全排列 II(JAVA)

文章目录 491. 非递减子序列解题思路源码 46. 全排列解题思路源码 47. 全排列 II解题思路源码 总结 491. 非递减子序列 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 …

【ai-动态维基百科】

初体验 今日份分享&#xff0c; 有趣工具&#xff0c;ai-动态维基百科&#xff0c;Globe Explorer。 介绍&#xff1a;基于人工智能(gpt3.5, gpt4.0)的搜索引擎&#xff0c;帮助用户以视觉化的方式探索各种主题。它使用大型语言模型来理解查询&#xff0c;并生成详细的主题页面…

【Python】快速排序法 Leetcode 148. 排序链表

题目 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4] 代码 第一次使用朴素快速排序&#xff0c;基准值为头节点值&#xff0c;居然超时了&#xff0c;然后…

大模型学习笔记一

前言 随着人工智能的不断发展&#xff0c;机器学习这门技术也越来越重要&#xff0c;很多人都开启了学习机器学习&#xff0c;本文就介绍了机器学习的基础内容。 一、AI是什么&#xff1f; 二、大模型能干什么 大模型&#xff0c;全称「大语言模型」&#xff0c;英文「Large…

基于STC12C5A60S2系列1T 8051单片机的数码管显示IIC总线器件AT24C02记录单片机上电次数应用

基于STC12C5A60S2系列1T 8051单片机的数码管显示IIC总线器件AT24C02记录单片机上电次数应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍液晶显示器LCD1602简单介绍…

宏景eHR showmediainfo SQL注入漏洞复现

0x01 产品简介 宏景eHR人力资源管理软件是一款人力资源管理与数字化应用相融合,满足动态化、协同化、流程化、战略化需求的软件。 0x02 漏洞概述 宏景eHR showmediainfo接口处存在SQL注入漏洞,未经过身份认证的远程攻击者可利用此漏洞执行任意SQL指令,从而窃取数据库敏感…

python--不死兔子问题

def rabbit(n):if n < 3:return 1return rabbit(n - 1) rabbit(n - 3)if __name__ __main__:print(rabbit(4))

AWS入门实践-S3 跨区域复制

Amazon S3的跨区域复制&#xff08;Cross-Region Replication, CRR&#xff09;是一项功能&#xff0c;允许你自动、异步地复制存储桶中的对象到一个位于不同AWS区域的存储桶中。这项功能对于数据备份、地理冗余、数据本地化以及合规性要求等场景非常有用。 一、跨区域复制的工…

一致性hash问题(负载均衡原理)

一致性哈希问题 简介 一致性Hash是一种特殊的Hash算法&#xff0c;由于其均衡性、持久性的映射特点&#xff0c;被广泛的应用于负载均衡领域&#xff0c;如nginx和memcached都采用了一致性Hash来作为集群负载均衡的方案。 本文将介绍一致性Hash的基本思路&#xff0c;并讨论其…