React页面提示消息超时引发无限循环的修复方案求助
问题
我正在开发一款需展示用户创建成功或失败提示消息的应用,采用Deno的Fresh框架(基于Preact,可视为精简版React)。当前页面中,用户创建操作后的提示消息无法在3秒后自动消失,且浏览器控制台出现无限循环现象。
我的网页代码如下:
// import { useState } from "preact/hooks"; import { useState, useEffect } from "preact/hooks"; import { Handlers, PageProps } from "$fresh/server.ts"; import UserDb from "../database.ts"; interface CreatorProps { email: string, key: string } export default function Creator(props: CreatorProps) { const [alertPointer, setAlertPointer] = useState(0); const [alertInst, setAlertInst] = useState(""); useEffect(() => { function createAlert() { if (alertPointer === 0) { null; } else if (alertPointer === 1) { setAlertInst( <div class="bg-blue-100 border-t border-b border-blue-500 text-blue-700 px-4 py-3" role="alert"> <p class="font-bold">User Creation Succeeded</p> <p class="text-sm">A new user was added to the database</p> </div> ); } else { setAlertInst( <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert"> <strong class="font-bold">User Creation Failed</strong> <span class="block sm:inline">Are you sure you entered in a valid email and key?</span> <span class="absolute top-0 bottom-0 right-0 px-4 py-3"> <svg class="fill-current h-6 w-6 text-red-500" role="button" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><title>Close</title><path d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z"/></svg> </span> </div> ) } } const timeId = setTimeout(() => { // After 3 seconds set the show value to false setAlertPointer(0); setAlertInst(""); }, 3000) function removeItem() { console.log("rm item start"); clearTimeout(timeId) console.log("rm item finished"); console.log(timeId); console.log(alertPointer); console.log(alertInst); } createAlert(); removeItem(); }, [alertPointer, alertInst]); async function handleSubmit(event) { event.preventDefault(); const emailInput = event.target.email; const ageInput = event.target.key; const resp = await createNewUser(emailInput.value, ageInput.value); return resp }; async function createNewUser(email, key) { const rawPosts = await fetch('http://localhost:8000/api/createUser', { "method": "POST", "headers": { "content-type": "application/json" }, "body": JSON.stringify({ email: email, key: key, }) }); const respns = JSON.parse(await rawPosts.json()); if (respns.isUserCreated) { setAlertPointer(1); } else { setAlertPointer(2); } } return ( <div> {alertInst} <h1 class="text rounded-lg p-4 my-8"> Search </h1> <form method="post" onSubmit={async (e) => handleSubmit(e)}> <input class="center rounded-lg p-4 my-8" id="email" name="email" /> <input class="center rounded-lg p-4 my-8" id="key" name="key" /> <br /> <button class="px-5 py-2.5 text-sm font-medium bg-blue-600 rounded-md shadow disabled:(bg-gray-800 border border-blue-600 opacity-50 cursor-not-allowed)" type="submit">Submit </button> </form> <br /> {/* <ul> {results.map((name) => <li key={name}>{name}</li>)} </ul> */} </div> ); };
创建用户成功时页面会显示蓝色的成功提示框,但提示消息从未消失,控制台会出现无限循环打印日志的现象。
修复方案
问题根源
- 无限循环触发:
useEffect依赖数组包含alertInst,但内部又调用setAlertInst修改该状态,导致useEffect反复执行形成循环;同时每次执行时立刻调用removeItem清除定时器,导致定时器根本无法触发,提示框无法自动消失。 - 冗余状态维护:
alertPointer和alertInst两个状态重复,完全可以通过alertPointer的值直接渲染提示内容,不需要额外维护alertInst。
具体修复步骤
- 移除
alertInst状态:直接在渲染阶段根据alertPointer的值生成对应的提示框,避免状态同步问题。 - 调整
useEffect逻辑:只监听alertPointer变化,当状态不为0时启动定时器;将定时器清理逻辑放在useEffect的返回函数中,这是Preact/React清理副作用的标准方式。 - 修复JSON解析错误:
rawPosts.json()已经返回解析后的对象,不需要再调用JSON.parse。
修复后的代码
import { useState, useEffect } from "preact/hooks"; import { Handlers, PageProps } from "$fresh/server.ts"; import UserDb from "../database.ts"; interface CreatorProps { email: string, key: string } export default function Creator(props: CreatorProps) { const [alertPointer, setAlertPointer] = useState(0); useEffect(() => { let timerId: number | undefined; // 仅当提示状态激活时启动定时器 if (alertPointer !== 0) { timerId = setTimeout(() => { setAlertPointer(0); }, 3000); } // 组件卸载或状态变化时清除定时器 return () => { if (timerId) clearTimeout(timerId); }; }, [alertPointer]); async function handleSubmit(event) { event.preventDefault(); const emailInput = event.target.email; const keyInput = event.target.key; await createNewUser(emailInput.value, keyInput.value); }; async function createNewUser(email, key) { const response = await fetch('http://localhost:8000/api/createUser', { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, key }) }); const respns = await response.json(); setAlertPointer(respns.isUserCreated ? 1 : 2); } // 根据状态渲染对应提示框 const renderAlert = () => { switch (alertPointer) { case 1: return ( <div class="bg-blue-100 border-t border-b border-blue-500 text-blue-700 px-4 py-3" role="alert"> <p class="font-bold">用户创建成功</p> <p class="text-sm">新用户已添加到数据库</p> </div> ); case 2: return ( <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert"> <strong class="font-bold">用户创建失败</strong> <span class="block sm:inline">请确认输入的邮箱和密钥是否有效?</span> <span class="absolute top-0 bottom-0 right-0 px-4 py-3 cursor-pointer" onClick={() => setAlertPointer(0)} > <svg class="fill-current h-6 w-6 text-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"> <title>关闭</title> <path d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z"/> </svg> </span> </div> ); default: return null; } }; return ( <div> {renderAlert()} <h1 class="text rounded-lg p-4 my-8"> 用户创建 </h1> <form method="post" onSubmit={handleSubmit}> <input class="center rounded-lg p-4 my-8" id="email" name="email" placeholder="输入邮箱" /> <input class="center rounded-lg p-4 my-8" id="key" name="key" placeholder="输入密钥" /> <br /> <button class="px-5 py-2.5 text-sm font-medium bg-blue-600 rounded-md shadow disabled:(bg-gray-800 border border-blue-600 opacity-50 cursor-not-allowed)" type="submit">提交 </button> </form> <br /> </div> ); };
额外优化
- 给失败提示框的关闭按钮添加点击事件,支持手动关闭
- 输入框添加占位符,优化用户体验
- 提示文本改为中文,适配国内用户习惯
内容的提问来源于stack exchange,提问作者ChristianOConnor
相关产品推荐
相关产品推荐

