Syncfusion:如何给日程编辑器窗口添加MultiSelectComponent字段
Syncfusion MultiSelectComponent集成Schedule编辑器窗口时触发TypeError: Cannot read properties of undefined (reading 'enqueueForceUpdate')
错误栈
Uncaught TypeError: Cannot read properties of undefined (reading 'enqueueForceUpdate') at Component.forceUpdate (react.development.js:368:16) at ComponentBase.renderReactTemplates (component-base.js:473:18) at Observer.eval (multi-select.js:281:23) at Observer.notify (observer.js:92:32) at ComponentBase.trigger (component-base.js:266:36) at MultiSelect.onPopupShown (multi-select.js:268:14) at Observer.eval (multi-select.js:5122:23) at Observer.notify (observer.js:92:32) at ComponentBase.trigger (component-base.js:266:36) at MultiSelect.showPopup (multi-select.js:5102:14) at MultiSelect.wrapperClick (multi-select.js:1099:22)
组件代码
"use client"; import * as React from "react"; import { ScheduleComponent, ViewsDirective, ViewDirective, Week, Month, Inject, } from "@syncfusion/ej2-react-schedule"; import { MultiSelectComponent } from "@syncfusion/ej2-react-dropdowns"; import { createElement } from "@syncfusion/ej2-base"; const Calendar = () => { const [eventData, setEventData] = React.useState([]); const onPopupOpen = (args) => { if (args.type === "Editor") { if (!args.element.querySelector(".custom-field-row")) { let row = createElement("div", { className: "custom-field-row" }); let formElement = args.element.querySelector(".e-schedule-form"); formElement.firstChild.insertBefore( row, formElement.firstChild.firstChild ); let container = createElement("div", { className: "custom-field-container", }); let inputEle = createElement("input", { className: "e-field", attrs: { name: "Members" }, }); container.appendChild(inputEle); row.appendChild(container); new MultiSelectComponent({ dataSource: [ { id: 1, text: "John Doe", email: "john.doe@example.com", }, { id: 2, text: "Jane Smith", email: "jane.smith@example.com", }, { id: 3, text: "Alice Johnson", email: "alice.johnson@example.com", }, ], fields: { text: "text", value: "id" }, placeholder: "Select Members", mode: "Box", showDropDownIcon: true, allowFiltering: true, }).appendTo(inputEle); } } }; const onActionBegin = (args) => { if (args.requestType === "eventCreate") { console.log("New appointment created:", args.data); setEventData([...eventData, ...args.data]); } if (args.requestType === "eventChange") { console.log("Appointment updated:", args.data); setEventData( eventData.map((event) => event.Id === args.data.Id ? args.data : event ) ); } if (args.requestType === "eventRemove") { console.log("Appointment removed:", args.data); setEventData(eventData.filter((event) => event.Id !== args.data[0].Id)); } }; return ( <div className="mt-10"> <ScheduleComponent height="800px" eventSettings={{ dataSource: eventData, allowMultiDrag: false, }} actionBegin={onActionBegin} popupOpen={onPopupOpen} allowMultiRowSelection={false} allowMultiCellSelection={false} > <ViewsDirective> <ViewDirective option="Week" /> <ViewDirective option="Month" /> </ViewsDirective> <Inject services={[Week, Month]} /> </ScheduleComponent> </div> ); }; export default Calendar;
已尝试的方案
- 确保MultiSelectComponent在onPopupOpen函数内初始化
- 检查依赖版本一致性
- 尝试用useRef方式初始化MultiSelectComponent
关键观察
使用普通DropdownListComponent时无此问题,仅MultiSelect出现该错误。
解决思路及修复方案
错误根因
当前用new MultiSelectComponent().appendTo(inputEle)的方式是Syncfusion原生JS组件的初始化逻辑,但MultiSelectComponent是React组件,这种方式会让它脱离React的组件上下文,导致组件内部调用forceUpdate时找不到React的enqueueForceUpdate方法——React组件必须挂载在React管理的组件树中,才能获取到更新队列。
修复方法
改用React的方式渲染MultiSelect组件,通过ReactDOM.render将其挂载到创建的DOM容器中,确保它处于React上下文:
- 导入
ReactDOM:
import * as ReactDOM from 'react-dom/client';
- 修改
onPopupOpen中的MultiSelect初始化逻辑:
const onPopupOpen = (args) => { if (args.type === "Editor") { if (!args.element.querySelector(".custom-field-row")) { let row = createElement("div", { className: "custom-field-row" }); let formElement = args.element.querySelector(".e-schedule-form"); formElement.firstChild.insertBefore(row, formElement.firstChild.firstChild); // 创建React根容器,而非普通input let container = createElement("div", { className: "custom-field-container" }); row.appendChild(container); // 使用ReactDOM渲染MultiSelect组件 const root = ReactDOM.createRoot(container); root.render( <MultiSelectComponent dataSource={[ { id: 1, text: "John Doe", email: "john.doe@example.com" }, { id: 2, text: "Jane Smith", email: "jane.smith@example.com" }, { id: 3, text: "Alice Johnson", email: "alice.johnson@example.com" }, ]} fields={{ text: "text", value: "id" }} placeholder="Select Members" mode="Box" showDropDownIcon={true} allowFiltering={true} name="Members" // 设置name属性对应表单字段 /> ); } } };
- 额外优化:清理React根避免内存泄漏
监听Schedule的popupClose事件,销毁创建的React根:
const onPopupClose = (args) => { if (args.type === "Editor") { const container = args.element.querySelector(".custom-field-container"); if (container) { const root = ReactDOM.createRoot(container); root.unmount(); container.remove(); } } };
然后在ScheduleComponent中添加popupClose={onPopupClose}属性。
为什么DropdownList没问题?
DropdownList的原生JS版本和React版本的边界处理逻辑不同,它在非React上下文初始化时未触发需要React更新队列的操作;而MultiSelect的Box模式等特性依赖React的模板渲染,必须处于React上下文才能正常工作。
内容的提问来源于stack exchange,提问作者FD3
相关产品推荐
相关产品推荐

