React+TypeScript中FullCalendar拖拽多事件及列表更新问题求助
问题:拖拽FullCalendar生成多个事件且事件列表不更新
在React+TypeScript项目中集成FullCalendar和Draggable组件时,拖拽「Bölgeler」容器内的块到日历,会生成多个重复事件,同时状态中的事件列表无法正确同步到日历。
原代码
import FullCalendar from "@fullcalendar/react"; import timeGridPlugin from "@fullcalendar/timegrid"; import interactionPlugin, { Draggable, DropArg, } from "@fullcalendar/interaction"; import allLocales from "@fullcalendar/core/locales-all"; import React, { useEffect, useState } from "react"; interface MyEvent { id: string; title: string; date: string; color: string; } function App() { const [events, setEvents] = useState<MyEvent[]>([]); useEffect(() => { const containerEl = document.querySelector("#bolgeler"); if (containerEl instanceof HTMLElement) { new Draggable(containerEl, { itemSelector: ".bolge", eventData: (eventEl) => { return { id: Math.floor(Math.random() * 1000).toString(), title: eventEl.innerText, color: window.getComputedStyle(eventEl).backgroundColor, }; }, }); console.log(events); } else { console.error("Container element not found"); } }, []); const handleEventDrop = (dropInfo: DropArg) => { const { draggedEl, date } = dropInfo; const title = draggedEl.innerText; const color = window.getComputedStyle(draggedEl).backgroundColor; const newEvent = { id: Math.floor(Math.random() * 1000).toString(), title, date: date.toISOString(), color, }; setEvents((prevEvents) => [...prevEvents, newEvent]); console.log(events); }; const handleEventRemove = (eventToRemoveId: string) => { setEvents((prevEvents) => prevEvents.filter((event) => event.id !== eventToRemoveId) ); }; return ( <> <div id="takvim"> <FullCalendar windowResizeDelay={100} handleWindowResize={true} height={650} plugins={[timeGridPlugin, interactionPlugin]} slotLabelFormat={{ hour: "2-digit", minute: "2-digit", }} editable={true} droppable={true} allDaySlot={false} dayHeaderFormat={{ weekday: "long", }} locales={allLocales} locale="tr" buttonText={{ today: "Bugün", }} events={{ events }} drop={() => handleEventDrop} eventClick={(info) => { if ( window.confirm( `Are you sure you want to delete the event '${info.event.id}'?` ) ) { handleEventRemove(info.event.id); } }} /> </div> <div id="bolgeler"> <p> <strong>Bölgeler</strong> </p> <div className="bolge" style={{ backgroundColor: "red" }}> Bölge 1 </div> <div className="bolge" style={{ backgroundColor: "green" }}> Bölge 2 </div> <div className="bolge" style={{ backgroundColor: "blue" }}> Bölge 3 </div> <div className="bolge" style={{ backgroundColor: "orange" }}> Bölge 4 </div> <div className="bolge" style={{ backgroundColor: "brown" }}> Bölge 5 </div> </div> </> ); } export default App;
核心问题分析
- 重复事件原因:同时开启了Draggable的
eventData自动生成事件,以及手动在drop事件中添加事件,导致两次创建事件。 - 事件列表不更新原因:FullCalendar的
events属性错误传递为{{ events }}(对象嵌套),而非直接传递事件数组events;同时drop事件绑定错误,写成了() => handleEventDrop,导致回调未正确触发。 - 日志无效原因:
setState是异步操作,直接打印events无法获取最新状态。
修复方案
1. 修正FullCalendar属性绑定
- 将
drop={() => handleEventDrop}改为drop={handleEventDrop},确保事件回调正确触发。 - 将
events={{ events }}改为events={events},让日历直接读取状态中的事件数组。
2. 移除Draggable自动生成事件的配置
删除Draggable中的eventData选项,避免自动添加事件,仅保留拖拽交互能力:
new Draggable(containerEl, { itemSelector: ".bolge", // 移除eventData配置 });
3. 修复状态日志问题
通过useEffect监听events变化,打印最新状态:
useEffect(() => { console.log("当前事件列表:", events); }, [events]);
4. 优化DOM元素获取(可选)
用React的useRef代替document.querySelector,更符合组件化规范:
const bolgelerRef = useRef<HTMLDivElement>(null); useEffect(() => { if (bolgelerRef.current) { new Draggable(bolgelerRef.current, { itemSelector: ".bolge", }); } }, []); // 绑定到容器元素 <div id="bolgeler" ref={bolgelerRef}>
5. 确保事件ID唯一性(可选)
替换随机ID生成逻辑,避免重复:
id: `${Date.now()}-${Math.floor(Math.random() * 1000)}`,
修复后完整代码
import FullCalendar from "@fullcalendar/react"; import timeGridPlugin from "@fullcalendar/timegrid"; import interactionPlugin, { Draggable, DropArg, } from "@fullcalendar/interaction"; import allLocales from "@fullcalendar/core/locales-all"; import React, { useEffect, useState, useRef } from "react"; interface MyEvent { id: string; title: string; date: string; color: string; } function App() { const [events, setEvents] = useState<MyEvent[]>([]); const bolgelerRef = useRef<HTMLDivElement>(null); useEffect(() => { if (bolgelerRef.current) { new Draggable(bolgelerRef.current, { itemSelector: ".bolge", }); } }, []); useEffect(() => { console.log("当前事件列表:", events); }, [events]); const handleEventDrop = (dropInfo: DropArg) => { const { draggedEl, date } = dropInfo; const title = draggedEl.innerText; const color = window.getComputedStyle(draggedEl).backgroundColor; const newEvent = { id: `${Date.now()}-${Math.floor(Math.random() * 1000)}`, title, date: date.toISOString(), color, }; setEvents((prevEvents) => [...prevEvents, newEvent]); }; const handleEventRemove = (eventToRemoveId: string) => { setEvents((prevEvents) => prevEvents.filter((event) => event.id !== eventToRemoveId) ); }; return ( <> <div id="takvim"> <FullCalendar windowResizeDelay={100} handleWindowResize={true} height={650} plugins={[timeGridPlugin, interactionPlugin]} slotLabelFormat={{ hour: "2-digit", minute: "2-digit", }} editable={true} droppable={true} allDaySlot={false} dayHeaderFormat={{ weekday: "long", }} locales={allLocales} locale="tr" buttonText={{ today: "Bugün", }} events={events} drop={handleEventDrop} eventClick={(info) => { if ( window.confirm( `Are you sure you want to delete the event '${info.event.title}'?` ) ) { handleEventRemove(info.event.id); } }} /> </div> <div id="bolgeler" ref={bolgelerRef}> <p> <strong>Bölgeler</strong> </p> <div className="bolge" style={{ backgroundColor: "red" }}> Bölge 1 </div> <div className="bolge" style={{ backgroundColor: "green" }}> Bölge 2 </div> <div className="bolge" style={{ backgroundColor: "blue" }}> Bölge 3 </div> <div className="bolge" style={{ backgroundColor: "orange" }}> Bölge 4 </div> <div className="bolge" style={{ backgroundColor: "brown" }}> Bölge 5 </div> </div> </> ); } export default App;
内容的提问来源于stack exchange,提问作者canerkaraca
相关产品推荐
相关产品推荐

