Fix duplicate events in fullcalendar in React

Viewed 422

I am developing a calendar using fullcalendar.io, but there is a problem. If you drop a schedule from the To Do list to the calendar, two duplicate events appear on the calendar. As the state is updated, one is displayed on the calendar, and the one provided by default is also displayed, so the two overlap. How can i fix this?

This is my code:

import React,{useEffect,memo, useRef, useState} from 'react';
import Fullcalendar from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin,{Draggable} from '@fullcalendar/interaction';
import styled from 'styled-components';
import { event } from 'jquery';

const Container  =  styled.div`
    
display:flex;
justify-Content: space-around;
alignItems:start;

    & .calendar{
        width:60%;
        height:auto;
    }
    & .Todo{
        width:300px;
            & h2{
                text-align:center;
            }
    }
`




// 이벤트 Todolist 에서 drag n drop 기능
    const ToDoList = memo(({event})=>{
        const elRef = useRef(null);
        
        useEffect(() => {
            const draggable = new Draggable(elRef.current, {
            eventData: function () {
                return { ...event, create: true };

            }
            },[]);
        
            return () => draggable.destroy(event);
        });
        return (
            <div
              ref={elRef}
              className="fc-event fc-h-event mb-1 fc-daygrid-event fc-daygrid-block-event p-2"
              title={event.title}
              style={{
                marginTop:"10px",
                cursor: "pointer",
                background:"#8b00ff",
                border:" none",
                padding:"5px",
              }}
            >
              <div className="fc-event-main">
                <div>
                  <strong>{event.title}</strong>
                </div>
              </div>
            </div>
          );
    
    });





function Calendar(){

    const calendarRef = useRef()

    // 캘린더 안에 들어있는 이벤트들 
    const [CalEvents, setEvents]  = useState([ 
        {
            id : 2341, 
            title: '학원 수업',
            color: 'red',
            start: '2021-10-19T11:00',
            end: '2021-10-19T13:00',
            constraint:'학원 수업'  // event 수정 제한
        },
        {
            id : 57124,
            title: '학원 수업',
            color: 'red',
            start: '2021-10-21T11:00',
            end: '2021-10-21T13:00',
            constraint:'학원 수업'  // event 수정 제한
        }
    ]);

    window.calevent = CalEvents;

    // Todolist 안에 들어있는 이벤트들
    const [Todo , setTodo]  = useState([
        {
            title:"과제 1 단어",
            id: 14351,
            start:' ',
            end: ' ',
            color:"#8b00ff",
        },
        {
            title:"수능 특강 풀기",
            id: 14203,
            start:' ',
            end: ' ',
            color:"#8b00ff",
        },
        {
            title:"개념 복습하기",
            id: 15151,
            start:' ',
            end: ' ',
            color:"#8b00ff",
        },
        {
            title:"모의고사1 다시 풀기",
            id: 16131,
            start:' ',
            end: ' ',
            color:"#8b00ff",
        },
        {
            title:"오답노트 쓰기",
            id: 15678,
            start:' ',
            end: ' ',
            color:"#8b00ff",
        }
    ]);

    // TodoLIst에서 캘린더로 드롭 후 State 업데이트 함수
    const handleDrop = (info) => {
        
        const new_start = info.event.startStr;
        const new_end = info.event.endStr;
        const NewEvent ={
     
            id:info.event.id,
            title:info.event.title,
            color:"#8b00ff",
            start: new_start,
            end:new_end,
            
        }
        //  state 변경해주기
        const DropList = CalEvents.concat(NewEvent);
        setEvents(DropList);
        console.log(CalEvents)
        CalEvents.addEventSource( NewEvent )
    }







    return(
        <Container>
            <div className="calendar">
                <Fullcalendar
                id="fullcalendar"
                plugins={[dayGridPlugin,interactionPlugin]}
                initialView = "dayGridMonth"
                selectable={true}  // 달력에서 드래그로 날짜 선택 
                editable={true} // 캘린더 내에서 일정 옮기고 수정  
                locale='ko' // 한국어 설정
                dayMaxEvents={true} // 하나의 날짜에 이벤트 갯수 제한 넘어가면 more로 표시 
                businessHours={true} // 주말 색깔 블러 처리
                events ={CalEvents} // calendar event 불러오기
                eventReceive = {handleDrop} // Todolist 에서 event를 드롭했을 때 state 업데이트
  
                
                
                />
             </div>

             <div className="Todo">
                 <h2>To Do List</h2>
                    <div className="list">
                        {Todo.map((event,index)=>{
                            return(
                            <ToDoList key ={index} event={event}/>
                        )})}
                    </div>
             </div>


        </Container>


    )
}

export default Calendar;
1 Answers

I had this exact same issue when using FullCalendar (with React, although this probably isnt react-specific), drag-and-drop event creation, and some kind of data storage to persist events to (in this case you are using react state for this). After some digging, here's what happens to cause you to see duplicate events:

  1. load events from storage and pass them into FullCalendar's events react prop to display the calendar
  2. when a new external event is dropped onto the calendar, FullCalendar creates a new event and adds it to its own internal event storage. At the same time, you add the event to your own data storage with a custom id value.
  3. everything looks fine (only one event is visible) until your data source is refreshed and the new data is passed into fullcalendar's event prop. FullCalendar likely takes this data and merged it with its own existing data, using the id fields to deduplicate.
  4. FullCalendar sees two events for the same day, one with a default id of "" (empty string) that it created earlier, and the other with your custom id value and concludes that they must be different, and so it displays both

The Easiest Solution

Probably the simplest way to resolve this is to ensure that when you call new Draggable(), make sure that your eventData is providing a correct id value that will uniquely identify that event and matches what will be in your database for that event.

This will ensure that the id's match when FullCalendar tries to deduplicate the event it created when the event was dropped with the new event it sees coming from your own data storage.

An alternative solution

If the items you are drag-and-dropping represent something other than an event (such as a TODO item, a schedule, a person, or anything else that could be dragged to multiple dates) you likely wont be able to just use an ID from that non-event object since the ID will be the same even if you create two calendar events from that object (which will mean the id is no longer unique and fullcalendar will likely delete one of your events).

after making sure your events have unique id's, you could make use of FullCalendar's eventsSet prop thats available in the react interface.

This function is called whenever any events change (wither via drag and drop or because the events prop changed value. When it is called, it is passed a list of every event that FullCalendar knows about. This is a good spot to add some of your own deduplication logic. For example, here's my version of this function:

eventsSet={(arg: EventApi[]) => {

    for (const event of arg) {
        //see how many events are set for the same day as this particular event
        let argsThisDay = arg.filter((value) => value.startStr == event.startStr)
        let argsThisDaycount = argsThisDay? argsThisDay.length: undefined;
        // if more than one event is present for today, and this event has an empty ID (i.e. it was created automatically by FullCalendar) we don't need it anymore as there is already another event covering this day
        if (argsThisDaycount == 2 && event.id == "") {
            event.remove()
        }
    }
}}

NOTE This function might not work for you as my app limits items on the calendar to one per day. You may need to customize this if your needs are different

Related