React中soupRequest被重置为初始值的原因及修复方案
问题修复方案
核心问题分析
- 初始状态设计缺陷:
soupRequest初始值为null,导致第一次useEffect执行时需额外赋值,触发重复渲染。 - Cleanup逻辑错误:
useEffect的cleanup函数中设置setisLooading(true),每次soupRequest更新时强制切换到Loading状态,卸载ControlPanel导致临时输入状态丢失。 - 子组件状态不同步:
ControlPanel的本地状态tmpRequest仅在挂载时初始化,父组件soupRequest更新时无法同步,导致输入框显示异常。 - 错误捕获遗漏参数:
try/catch中未接收error参数,导致控制台报错中断流程。
分步修复代码
1. 修复LetterSoup.jsx
import { useEffect, useState } from 'react'; import ControlPanel from '../components/ControlPanel'; import Grid from '../components/Grid'; import Words from '../components/Words'; function LetterSoup() { // 直接设置默认参数值,避免初始null导致的额外逻辑 const [soupRequest, setsoupRequest] = useState({ gridNum: 15, wordNum: 10, charNum: 4 }); const [letterSoup, setletterSoup] = useState(null); const [isLoading, setisLoading] = useState(true); const childToParent = (childdata) => { setsoupRequest(childdata); }; const contents = isLoading ? ( <p>Loading...</p> ) : ( <div className="lettersoup_container"> <h1 id="tabelLabel">Sopa de letras (PT)</h1> <div className="controlpanel_container"> <ControlPanel soupRequest={soupRequest} childToParent={childToParent} /> </div> <div>{letterSoup?.wordsNum} palavras</div> {/* <div className="grid_container"> <Grid board={letterSoup.board} /> </div> <div className="words_container"> <Words words={letterSoup.words} /> </div> */} </div> ); useEffect(() => { let ignore = false; async function fetchData() { setisLoading(true); // 发起请求时才设置Loading const aux = `/lettersoup/${soupRequest.gridNum}/${soupRequest.wordNum}/${soupRequest.charNum}`; try { const response = await fetch(aux); if (!ignore && response.ok) { const data = await response.json(); setletterSoup(data); setisLoading(false); } else if (!ignore) { throw new Error("Requisição falhou"); } } catch (error) { // 接收error参数 if (!ignore) { console.log(error.message); setisLoading(false); } } } fetchData(); return () => { ignore = true; // 移除cleanup中的setisLoading(true),避免不必要的状态切换 }; }, [soupRequest]); return (<div>{contents}</div>); } export default LetterSoup;
2. 修复ControlPanel.jsx
import { useId, useState, useEffect } from 'react'; function ControlPanel({ soupRequest, childToParent }) { const inputId = useId(); const [tmpRequest, setTmpRequest] = useState(soupRequest); // 同步父组件的soupRequest到本地状态 useEffect(() => { setTmpRequest(soupRequest); }, [soupRequest]); // 使用函数式更新,避免闭包问题 const handleGridChange = (e) => { setTmpRequest(prev => ({ ...prev, gridNum: parseInt(e.target.value) })); }; const handleWordChange = (e) => { setTmpRequest(prev => ({ ...prev, wordNum: parseInt(e.target.value) })); }; const handleCharChange = (e) => { setTmpRequest(prev => ({ ...prev, charNum: parseInt(e.target.value) })); }; // 阻止表单默认提交行为,避免页面刷新 const handleSubmit = (e) => { e.preventDefault(); childToParent(tmpRequest); }; return ( <form className="controlpanel" onSubmit={handleSubmit}> <div className="controlpanel_settings"> <div className="controlapanel_setting"> <label htmlFor={inputId}>Tamanho da grelha</label> <input type="number" value={tmpRequest.gridNum} min="10" max="25" step="5" id={inputId} name="gridNum" onChange={handleGridChange} className="controlpanel_setting_input" /> </div> <div className="controlapanel_setting"> <label htmlFor={`${inputId}-1`}>Número de palavras</label> <input type="number" value={tmpRequest.wordNum} min="10" max="50" step="1" id={`${inputId}-1`} name="wordNum" onChange={handleWordChange} className="controlpanel_setting_input" /> </div> <div className="controlapanel_setting"> <label htmlFor={`${inputId}-2`}>Mínimo de letras</label> <input type="number" value={tmpRequest.charNum} min="3" max="20" step="1" id={`${inputId}-2`} name="charNum" onChange={handleCharChange} className="controlpanel_setting_input" /> </div> </div> <div className="controlpanel_button"> <button type="submit">GO</button> </div> </form> ); } export default ControlPanel;
关键修改说明
- 初始状态赋值:给
soupRequest设置默认参数,避免null值导致的重复useEffect触发和额外状态更新。 - 调整Loading时机:仅在发起请求时设置
isLoading=true,移除cleanup中的状态切换,保证ControlPanel不会被频繁卸载。 - 子组件状态同步:通过
useEffect监听父组件soupRequest变化,同步更新本地临时状态,确保输入框显示最新值。 - 优化输入事件处理:使用函数式更新修改
tmpRequest,避免闭包导致的状态更新异常;阻止表单默认提交行为,防止页面刷新。 - 修复错误捕获:在
catch中接收error参数,确保错误信息能正常打印。
内容的提问来源于stack exchange,提问作者Rui Rodrigues
相关产品推荐
相关产品推荐

