jquense / react-big-calendar

gcal/outlook like calendar component
http://jquense.github.io/react-big-calendar/examples/index.html
MIT License
7.58k stars 2.2k forks source link

DragAndDrop with custom components causes re-mount on each render #2588

Open Poky85 opened 2 months ago

Poky85 commented 2 months ago

Check that this is really a bug

Reproduction link

https://github.com/Poky85/demo-react-big-calendar-issue

Bug description

When using Drag and drop addon in combination with custom components there is an issue with components being recreated on each render.

See the minimal reproduction repo. There I use custom EventWrapper component and log each remount in the console. It remounts although components passed in the prop are stable.

image


react-big-calendar@1.9.1 introduced change in custom components merge. In DragAndDrop class component there was mergeComponents() call moved from constructor to render function. See the pull request here.

This is bad approach. Now custom components gets recomputed on each render. React unmounts previous components and mounts them again because references (component functions) are not stable. This has negative impacts:

Expected Behavior

Custom components should not remount unless the items in components prop changes.

Actual Behavior

Custom components are remounted on each render.

react-big-calendar version

1.9.1

React version

18.3.1

Platform/Target and Browser Versions

macOS 14.2.1 + Chrome 124 / Firefox 125 / Safari 17.2.1

Validations

Would you like to open a PR for this bug?

TemaTarasov commented 1 month ago

We have the same issues, the root cause of the issue is that every render they create new factories of provided components.

I fixed that by moving this.components to a constructor, to provide more general behavior I would move that logic with merging + verification of links inside the shouldComponentUpdate function to prevent additional re-renders along with updating the this.components

...
  shouldComponentUpdate(nextProps, nextState) {
    if (!isEqual(this.props.components, nextProps.components)) {
      this.components = mergeComponents(nextProps.components, {
        eventWrapper: EventWrapper,
        eventContainerWrapper: EventContainerWrapper,
        weekWrapper: WeekWrapper,
      });
    }

    return true;
  }
...