React在useEffect中设置state触发Maximum update depth exceeded错误如何解决
问题原因及解决方案
死循环触发原因
你的代码产生无限重渲染的核心原因是 useEffect的依赖项和内部更新逻辑形成了闭环:
以处理来电数据的useEffect为例,你把incomingCalls放到了依赖列表中,同时effect内部又执行setIncomingCalls更新这个状态。状态更新后依赖项变化会再次触发effect执行,无限循环后导致页面无响应。
另外你维护了4个分类数据状态+1个合并数据状态,属于完全冗余的设计,也额外增加了状态同步的风险。
优化方案
直接移除冗余的5个状态,通过useMemo基于Redux获取的原始数据直接计算合并后的表格数据源即可,优化后代码如下:
import { useMemo } from 'react' // 仅保留必要的弹窗控制状态 const [showIncoming, setShowIncoming] = useState(false); const [showId, setShowId] = useState('') // 从Redux获取四类原始数据 const incomingCallList = useSelector((state) => state.incomingCallList); const { loading: loadingCalls, error: errorIncCalls, incomingCalls: incCalls, } = incomingCallList; const outgoingCallList = useSelector((state) => state.outgoingCallList); const { loading: loadingOutCalls, error: errorOutCalls, outgoingCalls: outCalls, } = outgoingCallList; const meetingList = useSelector((state) => state.meetingList); const { loading: loadingMeetings, error: errorMeetings, meetings: meetings_list, } = meetingList; const emailList = useSelector((state) => state.emailList); const { loading: loadingEmails, error: errorEmails, emails: email_list, } = emailList; // 拉取数据的逻辑保留 useEffect(() => { if(props.contractor._id) { dispatch(listIncomingCalls()) dispatch(listOutgoingCalls()) dispatch(listMeetings()) dispatch(listEmails()) } }, [props.contractor._id, dispatch]) const showIncomingModalHandler = id => { console.log(id) } // 用useMemo计算合并后的表格数据,仅在依赖变化时重新计算 const data = useMemo(() => { const mergedList = [] const targetContractorId = props.contractor._id // 转换并加入来电数据 if(incCalls) { const transformedIncoming = incCalls .filter(call => call.contractor._id === targetContractorId) .map(c => ({ type: 'Incoming Call', contact_person: c.contact_person, logged_by: c.created_by.name, date: c.time_created, link: <Button onClick={() => showIncomingModalHandler(c._id)}><Eye /></Button> })) mergedList.push(...transformedIncoming) } // 转换并加入去电数据 if(outCalls) { const transformedOutgoing = outCalls .filter(call => call.contractor._id === targetContractorId) .map(c => ({ type: 'Outgoing Call', contact_person: c.contact_person, logged_by: c.created_by.name, date: c.time_created, link: <> <LinkContainer to={`/outgoing-calls/${c._id}`} className='text-success' style={{ cursor: 'pointer'}}><Eye /></LinkContainer> </> })) mergedList.push(...transformedOutgoing) } // 转换并加入会议数据 if(meetings_list) { const transformedMeeting = meetings_list .filter(meeting => meeting.contractor._id === targetContractorId) .map(m => ({ type: 'Meeting', contact_person: m.held_with, logged_by: m.logged_by.name, date: m.meeting_time, link: <> <LinkContainer to={`/meeting/${m._id}`} className='text-success' style={{ cursor: 'pointer'}}><Eye /></LinkContainer> </> })) mergedList.push(...transformedMeeting) } // 转换并加入邮件数据 if(email_list) { const transformedEmail = email_list .filter(email => email.contractor._id === targetContractorId) .map(e => ({ type: 'Email', contact_person: e.contact_person, logged_by: e.logged_by.name, date: e.date, link: <> <LinkContainer to={`/emails/${e.contractor._id}/`} className='text-success' style={{ cursor: 'pointer'}}><Eye /></LinkContainer> </> })) mergedList.push(...transformedEmail) } return mergedList }, [incCalls, outCalls, meetings_list, email_list, props.contractor._id]) // 列配置保留 const columns = [ { name: 'Action', selector: 'link', sortable: true }, { name: 'Activity Type', selector: 'type', sortable: true }, { name: 'Contact Person', selector: 'contact_person', sortable: true }, { name: 'Logged By', selector: 'logged_by', sortable: true }, { name: 'Date', selector: 'date', sortable: true } ]
优化收益
- 彻底消除了循环依赖导致的无限重渲染问题
- 移除了冗余的状态维护逻辑,代码更简洁易读
- 数据源计算逻辑集中,后续修改转换规则只需修改
useMemo内部逻辑即可,维护成本更低
内容的提问来源于stack exchange,提问作者BranOIE
相关产品推荐
相关产品推荐

