拖拽触发Maximum update depth exceeded警告的解决问询
解决Grid拖拽至Scheduler时的"Maximum update depth exceeded"错误
问题描述
实现了Grid条目拖拽到Scheduler的功能,功能正常,但添加第6个条目并多次拖拽后,出现Warning: Maximum update depth exceeded错误。将useEffect依赖设为[dragItem]后错误消失,但拖拽功能无法正常工作。
App组件代码
import * as React from "react"; import * as ReactDOM from "react-dom"; import { Scheduler, WeekView, MonthView } from "@progress/kendo-react-scheduler"; import { guid } from '@progress/kendo-react-common'; import { Grid, GridColumn } from "@progress/kendo-react-grid"; import gridData from "./data.js"; const App = () => { const MyScheduler = React.createRef(); const [data, setData] = React.useState([]); const [dragTitle, setDragTitle] = React.useState(""); const [dragItem, setDragItem] = React.useState(""); const handleDropItem = e => { let start = e.target.getAttribute("data-slot-start"); let end = e.target.getAttribute("data-slot-end"); let startDate = new Date(parseInt(start)); let endDate = new Date(parseInt(end)); let newEvent = { id: guid(), title: dragItem.title, StartTimezone: null, start: startDate, end: endDate }; setData([newEvent, ...data]); }; React.useEffect(() => { let schedulerElement = MyScheduler.current.element; schedulerElement.addEventListener("drop", handleDropItem); schedulerElement.addEventListener("dragover", e => e.preventDefault()); }); const GridRowRender = (tr, props) => { const trProps = { draggable: true, onDragStart: e => { setDragItem(props.dataItem) } }; return React.cloneElement(tr, { ...trProps }, tr.props.children); }; return ( <> <Scheduler data={data} defaultDate={new Date("2013/6/13")} ref={MyScheduler}> <WeekView showWorkHours={false} /> <MonthView /> </Scheduler> <hr /> <Grid data={gridData} rowRender={GridRowRender}> <GridColumn field='taskID' /> <GridColumn field='title' /> </Grid> </> ); }; ReactDOM.render(<App />, document.querySelector("my-app"));
data.js代码
const data = [ { taskID: 1, ownerId: 1, title: 'Fast and furious 6', }, { taskID: 2, ownerId: 3, title: 'The Internship', }, { taskID: 3, ownerId: 2, title: 'The Perks of Being a Wallflower', }, { taskID: 4, ownerId: 1, title: 'The Help', }, { taskID: 5, ownerId: 2, title: 'Now You See Me', }, { taskID: 6, ownerId: 2, title: 'Sixth Item', }, ]; export default data;
错误原因分析
- 重复绑定事件监听器:
useEffect未设置依赖数组和清理函数,组件每次渲染(如data更新、dragItem变化)都会给Scheduler元素重复添加drop和dragover监听器。多次拖拽后,单次drop操作会触发多个handleDropItem调用,频繁触发状态更新,最终超出React更新深度限制。 - 状态更新依赖旧值:
handleDropItem直接使用data状态,闭包特性导致函数捕获渲染时的旧data值,可能引发状态更新异常,加剧重复渲染。 - 不必要的组件重渲染:
GridRowRender和handleDropItem每次渲染都会重新创建,导致Grid行和事件监听器频繁更新,增加渲染压力。
解决方案
以下修改可彻底解决错误并保持功能正常:
import * as React from "react"; import * as ReactDOM from "react-dom"; import { Scheduler, WeekView, MonthView } from "@progress/kendo-react-scheduler"; import { guid } from '@progress/kendo-react-common'; import { Grid, GridColumn } from "@progress/kendo-react-grid"; import gridData from "./data.js"; const App = () => { const MyScheduler = React.createRef(); const [data, setData] = React.useState([]); const [dragItem, setDragItem] = React.useState(null); // 用useCallback稳定函数引用,依赖dragItem确保获取最新拖拽项 const handleDropItem = React.useCallback((e) => { if (!dragItem) return; const start = e.target.getAttribute("data-slot-start"); const end = e.target.getAttribute("data-slot-end"); if (!start || !end) return; const startDate = new Date(parseInt(start)); const endDate = new Date(parseInt(end)); const newEvent = { id: guid(), title: dragItem.title, StartTimezone: null, start: startDate, end: endDate }; // 函数式更新获取最新data状态 setData(prevData => [newEvent, ...prevData]); }, [dragItem]); // 用useCallback稳定GridRowRender引用,避免不必要的行重渲染 const GridRowRender = React.useCallback((tr, props) => { const trProps = { draggable: true, onDragStart: () => { setDragItem(props.dataItem); } }; return React.cloneElement(tr, trProps, tr.props.children); }, []); React.useEffect(() => { const schedulerElement = MyScheduler.current?.element; if (!schedulerElement) return; const handleDragOver = (e) => e.preventDefault(); schedulerElement.addEventListener("drop", handleDropItem); schedulerElement.addEventListener("dragover", handleDragOver); // 清理函数:移除事件监听器,避免内存泄漏和重复绑定 return () => { schedulerElement.removeEventListener("drop", handleDropItem); schedulerElement.removeEventListener("dragover", handleDragOver); }; }, [handleDropItem]); return ( <> <Scheduler data={data} defaultDate={new Date("2013/6/13")} ref={MyScheduler}> <WeekView showWorkHours={false} /> <MonthView /> </Scheduler> <hr /> <Grid data={gridData} rowRender={GridRowRender}> <GridColumn field='taskID' /> <GridColumn field='title' /> </Grid> </> ); }; ReactDOM.render(<App />, document.querySelector("my-app"));
修改要点说明
- 添加事件清理逻辑:在
useEffect返回函数中移除监听器,避免重复绑定导致的多次触发。 - 使用
useCallback稳定引用:确保handleDropItem和GridRowRender的引用在依赖不变时保持稳定,减少不必要的重渲染和事件绑定。 - 函数式状态更新:
setData(prevData => [...])确保每次更新基于最新状态,避免闭包导致的旧值问题。 - 增加边界判断:在
handleDropItem中检查拖拽项和时间槽参数,避免无效操作。
内容的提问来源于stack exchange,提问作者Murray Johnson
相关产品推荐
相关产品推荐

