React自定义Hook触发无限循环问题求助
React + Firebase任务管理器无限循环问题解决
问题背景
使用React和Firebase开发简易任务管理器,通过自定义useHttp Hook获取数据时,页面加载出现无限循环。需求是页面加载和提交新任务表单时各触发一次数据请求。
相关代码
触发Hook的组件代码
const AllTasks = () => { const [tasks, setTasks] = useState([]); const renderTasks = (taskObj) => { const loadedTasks = []; for (const key in taskObj) { loadedTasks.unshift({ id: key, date: taskObj[key].Date, client: taskObj[key].Client, task: taskObj[key].Task, time: taskObj[key].Time, }); } setTasks(loadedTasks); }; const { sendRequest: retrieveTasks } = useHttp(); // 原直接调用逻辑会触发无限循环,已注释 // const refresh = retrieveTasks( // { // url: "https://myurl.com/alltasks.json", // }, // renderTasks // ); // 页面加载时触发请求(仅执行一次) useEffect(() => { retrieveTasks( { url: "https://myurl.com/alltasks.json", }, renderTasks ); }, []); // 表单相关逻辑 const enteredDateRef = useRef(); const enteredClientRef = useRef(); const enteredTaskRef = useRef(); const enteredTimeRef = useRef(); const renderNewTask = (data) => { const taskData = { id: data.name, date: data.Date, client: data.Client, task: data.Task, time: data.Time, }; setTasks((tasks) => [taskData, ...tasks]); }; const { sendRequest: postTask } = useHttp(); const submitTaskHandler = async (event) => { event.preventDefault(); const enteredDate = enteredDateRef.current.value; const enteredClient = enteredClientRef.current.value; const enteredTask = enteredTaskRef.current.value; const enteredTime = enteredTimeRef.current.value; const newTaskData = { Date: enteredDate, Client: enteredClient, Task: enteredTask, Time: enteredTime, }; postTask( { url: "https://myurl.com/alltasks.json", method: "POST", body: newTaskData, // 修正Content-Type的拼写错误 headers: { "Content-Type": "application/json" }, }, renderNewTask ); }; // 修正refresh为函数,避免组件渲染时立即执行请求 const refresh = () => { retrieveTasks( { url: "https://myurl.com/alltasks.json", }, renderTasks ); }; return ( <Fragment> <TasksHeader /> <section className="w-full mt-4"> <CardDark backgroundType="liquid"> <h2>This month alone</h2> <div className="flex mt-4 justify-between"> <div> <p className=" text-6xl font-medium">56.4</p> <p className="mt-2">Hours input</p> </div> <div className="border border-white"></div> <div> <p className=" text-6xl font-medium">1,378.45 €</p> <p className="mt-2">Cash generated</p> </div> <div className="border border-white"></div> <div> <p className=" text-6xl font-medium">128</p> <p className="mt-2">Tasks input</p> </div> </div> </CardDark> </section> <section> <SearchBar /> <div className="flex justify-between mt-4"> <h2>Your tasks</h2> <div className="flex"> <h2 className="mr-2">Filters:</h2> <form className="text-gray-400" action=""> <input type="date" className=" bg-gray-50 w-[120px] mr-2" /> <input type="date" className=" bg-gray-50 w-[120px] mr-2" /> <select id="cars" name="cars" className="bg-gray-50"> <option value="volvo">Volvo</option> <option value="saab">Saab</option> <option value="fiat">Fiat</option> <option value="audi">Audi</option> </select> </form> </div> </div> </section> <section> <CardWhite> <form action="" onSubmit={submitTaskHandler} className="h-[50px] flex" > <input ref={enteredDateRef} type="date" className="h-[50px] focus:outline-none w-1/5 rounded-l-2xl border border-gray-300 px-4" /> <input ref={enteredClientRef} type="text" className="h-[50px] focus:outline-none w-1/5 border-y border-gray-300 px-4" placeholder="Client name" /> <input ref={enteredTaskRef} type="text" className="h-[50px] focus:outline-none grow border-y border-l border-gray-300 px-4" placeholder="Task" /> <div className="h-[50px] flex w-1/10 rounded-r-2xl border border-gray-300 pl-4"> <input ref={enteredTimeRef} type="time" className="focus:outline-none h-full" /> <button type="submit" className="h-full p-2"> <img src={SubmitIcon} className="h-full" alt="" /> </button> </div> </form> <List tasks={tasks} refresh={refresh} /> </CardWhite> </section> </Fragment> ); }; export default AllTasks;
自定义Hook代码
const useHttp = () => { const sendRequest = async (requestConfig, applyData) => { const response = await fetch(requestConfig.url, { method: requestConfig.method ? requestConfig.method : "GET", body: requestConfig.body ? JSON.stringify(requestConfig.body) : null, headers: requestConfig.headers ? requestConfig.headers : {}, }); const responseData = await response.json(); applyData(responseData); console.log('request'); }; return { sendRequest }; }; export default useHttp;
问题原因及解决方案
无限循环原因
组件渲染时直接调用retrieveTasks,每次渲染都会执行该函数,函数内部调用setTasks更新状态,触发组件重新渲染,再次执行retrieveTasks,形成无限循环。
具体修复步骤
- 启用页面加载请求逻辑:取消注释
useEffect代码块,将retrieveTasks调用放入其中,依赖项设为空数组[],确保仅在组件挂载时执行一次请求。 - 移除直接调用的请求:删除原直接调用
retrieveTasks并赋值给refresh的代码,避免每次渲染都触发请求。 - 修正refresh定义:将
refresh包装成函数,子组件调用时才触发请求,而非组件渲染时立即执行。 - 修复请求头错误:将
postTask中的"application.json"改为"application/json",确保Firebase能正确解析请求体。
修改后,页面加载时触发一次数据请求,提交表单时触发一次新增任务请求,不会再出现无限循环问题。
内容的提问来源于stack exchange,提问作者Lucas Vidal
相关产品推荐
相关产品推荐

