NextJS+ChakraUI实现未保存内容时离开页面弹出警告对话框
问题解决方案
问题1:对话框无法正常弹出
routeChangeStart触发后Next.js会默认执行页面跳转,导致对话框刚打开就被覆盖。需要检测到未保存更改时阻止默认导航,再弹出对话框。
问题2:确认后跳转到目标路由
触发导航事件时记录用户原本要跳转的目标路由,点击"YES"时手动执行跳转。
修改后的完整代码
import { Box, Grid, GridItem, Input, Flex, Button, useColorModeValue, } from "@chakra-ui/react"; import { AlertDialog, AlertDialogBody, AlertDialogFooter, AlertDialogHeader, AlertDialogContent, AlertDialogOverlay, useDisclosure, } from "@chakra-ui/react"; import PasswordEditor from "@/components/PasswordEditor"; import { useRouter } from "next/router"; import { postMethod, updateMethod } from "@/helpers/services"; import TopNav from "./TopNav"; import showMsg from "@/helpers/showMsg"; import { IRecordEditingProps, IRecordEditData } from "@/types/components"; import deepEqual from "deep-equal"; import { useState, useEffect, useRef } from "react"; const RecordEditing: React.FC<IRecordEditingProps> = ({ type, record, user, }) => { const [recordObj, setRecordObj] = useState<IRecordEditData>(record); const [password, setPassword] = useState<string>(record.password); const [targetRoute, setTargetRoute] = useState<string | null>(null); const title = type === "new" ? "New Record" : "Edit Record"; const router = useRouter(); const { recordId } = router.query; const { isOpen, onOpen, onClose } = useDisclosure(); const cancelRef = useRef<HTMLButtonElement>(null); const buttonBg = useColorModeValue("#dbdbdb", "#2a2c38"); const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { setRecordObj((prevState) => ({ ...prevState, [e.target.id]: e.target.value, })); }; const handleSubmit = () => { if (type === "new") { postMethod(`/api/user/${user.id}/records`, { ...recordObj, password, }) .then(() => router.push("/")) .catch(() => showMsg("Something went wrong", { type: "error" })); } else { updateMethod(`/api/user/${user.id}/records/${recordId}`, { ...recordObj, password, }) .then(() => router.push("/")) .catch(() => showMsg("Something went wrong", { type: "error" })); } }; useEffect(() => { const checkForChanges = (url: string) => { // 同时检查recordObj和password的变化 const hasUnsavedChanges = !deepEqual(recordObj, record) || password !== record.password; if (hasUnsavedChanges) { // 阻止默认导航 router.events.emit("routeChangeError"); // 记录目标路由 setTargetRoute(url); // 打开对话框 onOpen(); console.log("Warning for changes!!!!!!!!!!"); } }; router.events.on("routeChangeStart", checkForChanges); return () => { router.events.off("routeChangeStart", checkForChanges); }; }, [recordObj, record, password, router, onOpen]); // 点击YES后的跳转逻辑 const handleConfirmLeave = () => { if (targetRoute) { router.push(targetRoute); } onClose(); }; return ( <Box py="60px"> <TopNav title={title} type="backAndTitle" /> <Box> <Grid gridTemplateColumns="3fr 6fr" gap="10px" py="10px"> <GridItem w="100%" h="10"> <Flex align="center" h="100%"> Title </Flex> </GridItem> <GridItem w="100%" h="10"> <Input id="title" value={recordObj.title} placeholder="Record title" onChange={handleInputChange} _focusVisible={{ border: "2px solid", borderColor: "teal.200" }} /> </GridItem> <GridItem w="100%" h="10"> <Flex align="center" h="100%"> Url </Flex> </GridItem> <GridItem w="100%" h="10"> <Input id="url" value={recordObj.url} placeholder="Website url (optional)" onChange={handleInputChange} _focusVisible={{ border: "2px solid", borderColor: "teal.200" }} /> </GridItem> <GridItem w="100%" h="10"> <Flex align="center" h="100%"> Username </Flex> </GridItem> <GridItem w="100%" h="10"> <Input id="username" value={recordObj.username} placeholder="Username or email" onChange={handleInputChange} _focusVisible={{ border: "2px solid", borderColor: "teal.200" }} /> </GridItem> </Grid> </Box> <PasswordEditor password={password} setPassword={setPassword} /> <Box mt="20px"> <Button type="submit" w="100%" background={buttonBg} _focus={{ background: buttonBg }} onClick={handleSubmit} > Save Record </Button> </Box> <AlertDialog motionPreset="slideInBottom" leastDestructiveRef={cancelRef} onClose={onClose} isOpen={isOpen} isCentered > <AlertDialogOverlay /> <AlertDialogContent> <AlertDialogHeader> Are you sure you want to leave this page? </AlertDialogHeader> <AlertDialogBody> You have unsaved changes. Are you sure you want to leave this page? Unsaved changes will be lost! </AlertDialogBody> <AlertDialogFooter> <Button ref={cancelRef} onClick={onClose}> No </Button> <Button onClick={handleConfirmLeave} colorScheme="red" ml={3} > Yes </Button> </AlertDialogFooter> </AlertDialogContent> </AlertDialog> </Box> ); }; export default RecordEditing;
关键修改点说明
- 阻止默认导航:调用
router.events.emit("routeChangeError")取消默认跳转,确保对话框正常显示。 - 记录目标路由:新增
targetRoute状态存储跳转URL,点击"YES"时通过router.push(targetRoute)完成跳转。 - 完善变更检测:将
password加入变更判断,覆盖密码修改的未保存场景。 - 更新依赖数组:确保事件监听能正确响应状态变化。
内容的提问来源于stack exchange,提问作者Stef-Lev
相关产品推荐
相关产品推荐

