在useEffect中调用setState导致Jest测试出现最大更新深度超限错误
解决方案:分离基础数据与本地状态,用
useMemo派生计算值 你的核心需求是基于props传入的结构生成扁平化数据,同时支持本地修改(比如toggle状态),且props更新时自动同步扁平化数据。直接用useEffect同步props到state是常规做法,但如果想规避useEffect,可以通过分离基础数据和本地可变状态,结合useMemo实时派生最终的mappedThing,这是更符合React设计理念的标准方案。
具体改造步骤
- 修正工具函数的基础错误
数组没有append方法,需改为push;同时确保函数无副作用,返回全新数组:
const mapThing = (notFlatStructure) => { const flattenedStructure = []; notFlatStructure.forEach((mainItem) => { flattenedStructure.push({ id: mainItem.id, text: mainItem.text, isToggle: true }); if (mainItem.subItems?.length) { mainItem.subItems.forEach((subItem) => { flattenedStructure.push({ id: subItem.id, text: subItem.text, isToggle: true }); }); } }); return flattenedStructure; };
- 重构组件状态逻辑
不再把整个扁平化结构存在state中,仅存储需要本地修改的toggle状态;用useMemo实时合并props数据和本地状态:
const MyComponent = (props) => { // 仅存储被修改过的toggle状态,key为item的id,value为当前toggle值 const [toggleStates, setToggleStates] = useState({}); // 实时派生最终的mappedThing:props.thing变化时自动重新计算,同时合并本地修改 const mappedThing = useMemo(() => { const baseStructure = mapThing(props.thing); return baseStructure.map(item => ({ ...item, isToggle: toggleStates[item.id] ?? item.isToggle })); }, [props.thing, toggleStates]); // 修改toggle状态:仅更新对应id的状态,保持不可变更新 const handleClick = (row) => { setToggleStates(prev => ({ ...prev, [row.id]: !(prev[row.id] ?? true) // 默认初始toggle值为true })); }; const handleBlur = (e) => { props.thingChanged(e); }; return ( <div> {mappedThing.map((row) => ( <div key={row.id}> {/* 必须添加唯一key,避免React渲染警告 */} <input onBlur={handleBlur} value={row.text} /> <button onClick={() => handleClick(row)}>Toggle</button> </div> ))} </div> ); };
方案优势
- 无需
useEffect:useMemo会自动监听props.thing和toggleStates的变化,同步生成最新的扁平化结构,完全替代了原有的useEffect同步逻辑。 - 符合React状态规范:所有状态更新均为不可变操作,避免了直接修改state对象导致的React无法检测变化的问题,也能解决Jest测试中可能遇到的异步更新异常。
- 状态更精简:仅存储需要本地修改的toggle状态,而非整个扁平化结构,减少不必要的组件重渲染。
额外修正的代码问题
- 列表渲染添加了
key属性,符合React的列表渲染要求。 - 修复了
handleClick中find方法里的赋值错误(原代码用=而非===),同时规避了直接修改state对象的不良写法。
内容的提问来源于stack exchange,提问作者Andrew Cooper
相关产品推荐
相关产品推荐

