New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dragging events during ongoing backend requests results in duplicate events #7067
Comments
I'm not very familiar with Redux but this does look like a bug to me. Moving to React repo since it's specific to that. |
I have the same issue but in my case every movement of an event duplicates it. Even without dragging it second time. |
Seeing this, or similar behaviour with duplicated events, but in our case we are just using simple component state and not redux. |
Any timeplan for this @acerix ? It's literally preventing us from purchasing a license at this point. |
We're blocked by the same bug because we update the sources from the backend upon window focus. So when a user coming from a different window immediately drags an event it's duplicated every time. |
I'm having the same issue. I've found an acceptable workaround but would love to see the core issue resolved. For others having the same issue my solution was to create a secondary store of event data that drives the calendar and only allow that to be updated when you aren't actively dragging/resizing an event. You can use eventDragStart and eventResizeStart to set a state variable called updatePending to true and then set it to false with on eventDrop/Resize. The calendar reads the secondary store of event data which is automatically updated anytime data from the backend updates the primary store except when updatePending is true in which case the update is bypassed. There's always another update in the queue so you'll eventually update the second store. Slows the UI a bit and is overkill if you don't need to duplicate your event stores but I was doing it anyway as part of how I handle the event data in the rest of the application. |
@nabati , I assume this is your codepen? I can't seem to recreate the duplication. I can recreate the original problem however. The problem is occurring because the calendar is receiving what it considers to be a brand new event store, during a drag. By the time the dragging event is dropped, the calendar has lost reference to the original event, so the event being dragged is essentially considered an external event that is dropped onto the calendar, creating a new event. The bugfix here is to make the calendar smarter about knowing that events with the same However, what many other apps do while waiting for a network write-request to complete is to block user editing. I would suggest this as a workaround. You could have a I hope this helps as a workaround, although I know it is not ideal. |
Is there a chance to provide a solution earlier? Do you have a bug bounty program? |
I created a small monkey patch that worked for our case. Looking forward to some feedback. import { useMemo, useRef, useState } from 'react'
import { isUndefined, omitBy } from 'lodash'
// Unfortunately the current implementation of fullcalendar-react does not honor the `event.id`
// https://github.com/fullcalendar/fullcalendar/issues/7067
// This approach is a inefficient monkey patch which tries to fix the situation
//
// Basic approach is to keep a shadow representation of the events in `oldEventsRef`
// and calculate on every `newEvents` update the added, removed and updated based on the `event.id`.
// With this information we use the calendarApi to update/remove/add the events one by one
// We only update those, where the id is the same, but the object reference differs
//
// Minimal ExampleUsage:
// ```
// function MyCalendar({events}){
// const calendarRef = useRef()
// useFullcalendarEventsMonkeyPatch(
// events,
// calendarStore.calendarRef.current,
// )
// return <FullCalendar ref={calendarRef} />
// }
// ```
const DEFAULT_EVENTSOURCE_ID = 'monkeypatch-eventsource'
const DEFAULT_EVENTSOURCE = {
events: [],
id: DEFAULT_EVENTSOURCE_ID,
}
export default function useFullcalendarEventsMonkeyPatch(newEvents, calendarRef) {
const oldEventsRef = useRef([])
const [resources, setResources] = useState(new Map())
const calendarApi = calendarRef?.getApi()
// Make sure resources is set through the calendar callback to be used later
useMemo(() => {
if (!calendarApi) return null
const setResourcesAsMap = (newResources) => setResources(new Map(
newResources.map((newResource) => [newResource.id, newResource]),
))
calendarApi.on('resourcesSet', setResourcesAsMap)
return () => calendarApi.off('resourcesSet', setResourcesAsMap)
}, [calendarApi])
useMemo(() => {
if (!calendarRef) return
if (resources.size === 0) return
if (newEvents.length === 0 && oldEventsRef.current?.length === 0) return
// get the oldEvents from previous iteration
const oldEvents = oldEventsRef.current
// Calculate the items to be added, removed or updated we use the id in the raw events here
const added = newEvents.filter((newEvent) => !oldEvents.find((oldEvent) => oldEvent.id === newEvent.id))
const removed = oldEvents.filter((oldEvent) => !newEvents.find((newEvent) => oldEvent.id === newEvent.id))
// We only update those, where the id is the same, but the object reference differs
const updated = newEvents
.filter((newEvent) => oldEvents.find((oldEvent) => oldEvent.id === newEvent.id) !== newEvent)
// Get or create the monkeypatch-eventsource
const eventSource = calendarApi.getEventSourceById(DEFAULT_EVENTSOURCE_ID)
|| calendarApi.addEventSource(DEFAULT_EVENTSOURCE)
// Execute the addition, removal, updates on the calendar
added.forEach((event) => calendarApi.addEvent(event, eventSource))
removed.forEach((event) => calendarApi.getEventById(event.id)?.remove())
updated.forEach((event) => {
const calEvent = calendarApi.getEventById(event.id)
if (!calEvent) return
const {
start, end, allDay, resourceIds,
groupId, title, url, interactive, display, editable, startEditable, durationEditable,
constraint, overlap, allow, className, classNames, color, backgroundColor, borderColor, textColor,
...rest
} = event
// Set new times
calEvent.setStart(start)
calEvent.setEnd(end)
calEvent.setAllDay(allDay)
// Set resources
if (event.resourceIds) {
calEvent.setResources(event.resourceIds.map((resourceId) => resources.get(resourceId)))
}
// Set event properties
Object.entries(omitBy({
groupId,
title,
url,
interactive,
display,
editable,
startEditable,
durationEditable,
constraint,
overlap,
allow,
className,
classNames,
color,
backgroundColor,
borderColor,
textColor,
}, isUndefined)).forEach(([name, val]) => {
calEvent.setProp(name, val)
})
// Set event extended properties
Object.entries(rest).forEach(([name, val]) => {
calEvent.setExtendedProp(name, val)
})
})
oldEventsRef.current = newEvents
}, [
newEvents,
calendarRef,
resources,
])
} |
any updates on this one? |
|
Same issue on my side. I temporarily solved this by removing the event during the eventChange, sample code below ...
eventChange: this.handleEventChange.bind(this),
... handleEventChange(arg: EventChangeArg) {
arg.event.remove(); // due to duplicate bug
... |
Fixed since v6.1.0 Update repro: https://codesandbox.io/s/test-react-redux-typescript-forked-xki0xc?file=/src/calendar.tsx (drag the event around rapidly and you'll see duplicate events no longer get created. you will still see the delay in movement because of the delay of the request, but that's expected). Please ensure that every event object has an Please also ensure you use toPlainEvent when spreading an EventApi's props into your redux store. I have a note-to-self to make this all clearer in the docs |
That updated repro is still showing some problems. I'll revisit. |
@arshaw Thanks for the update! I recently solved this issue by lifting state up and was managing my async state manually, but that was a ton of work to get working right. Look forward to implementing changes from v6.1 |
This is finally fixed in v6.1.3 Update repro: It's important for everyone to have unique @pauldvu, glad you were able to work around the original issue. |
Using v6.1.4 and React 18 and still seeing this. I have a unique ID set in the eventData of each new Draggable and getting 5 or 6 events on each drop. Any tips? |
@cuttingedgecrm, would you mind opening a new ticket? |
Reduced Test Case
https://codesandbox.io/s/test-react-redux-typescript-forked-q05tx?file=/src/calendar.tsx
Bug Description
We're currently banging our heads against the wall with an issue with duplicate events whenever a user has initiated a drag and drop while an update to the redux state occurs. We've looked through examples/documentation on how to make FullCalendar work together with React/Redux (including https://github.com/fullcalendar/fullcalendar-example-projects/tree/master/react-redux by @arshaw ), but are still unable to figure out how we should fix the issue on our end.
Steps to reproduce:
Expected behaviour:
Actual behaviour:
Is this an issue with FullCalendar itself? Or are we using FullCalendar in the wrong way?
Help is greatly appreciated.
The text was updated successfully, but these errors were encountered: