bokuweb / react-rnd

🖱 A resizable and draggable component for React.
https://bokuweb.github.io/react-rnd/stories
MIT License
3.89k stars 324 forks source link

Memory Leak Using React Context #503

Closed techromantic closed 5 years ago

techromantic commented 5 years ago

Help Wanted / Browser Bug

Overview of the problem

I'm using react-rnd version [9.1.2]

My browser is: Chromium

Description

Using a data context within my application, rnd components are re-rendered from this data and can write to state using context methods. The error is within the react-draggable library, for the dragStop callback (the dragItemEnd method below). Within the drogStop callback, I call a context method which re-renders all the rnds.

Getting the following error: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method. in Draggable (created by Rnd) in Rnd (at dynamicCalendarItem.js:98) in DynamicCalendarItem (at calendarEntries.js:78)

When this happens, the rnd components re-render once, but are unable to fire the callback a second time. This doesn't happen with the resizingStop callback (this changeItemLength method below) - I've tried to look at the differences between the componentWillUnmount() methods of both libraries but its not making much sense.

Would really appreciate some advice - and thanks for the awesome library.

changeItemLength = (event, direction, delta, position, context) => {
    this.setState({
      editing: false
    });
    event.stopPropagation();
    let itemLengthDelta = Math.round(delta.height / this.props.itemHeight);
    context.handleCalendarTaskChangeLength(direction, this.state.task, itemLengthDelta);
  }

  dragItemEnd = (e, drag, context) => {
    e.stopPropagation();
    let change = Math.round((drag.y - this.lastPosition)/this.props.itemHeight);
    context.handleCalendarTaskReschedule(this.state.task, change);
  }
<Rnd
              key={this.props.y}
              className="dynamic_item_container"
              default={{
                x: 0,
                y: this.props.y,
                height: this.props.height,
                width: "100%"
              }}
              minHeight={this.props.itemHeight}
              resizeGrid={[this.props.itemHeight, this.props.itemHeight]}
              dragGrid={[this.props.itemHeight, this.props.itemHeight]}
              dragAxis="y"
              bounds="parent"
              onResizeStop={(e, dir, ref, delta, position) => this.changeItemLength(e, dir, delta, position, context)}
              onDragStart={(e, drag) => this.dragItemStart(e, drag)}
              onDragStop={(e, drag) => this.dragItemEnd(e, drag, context)}
              disableDragging={(this.state.editing) ? true : false}
              enableResizing={(this.state.editing) ? {
                bottom: false,
                bottomLeft: false,
                bottomRight: false,
                left: false,
                right: false,
                top: false,
                topLeft: false,
                topRight: false,
              } : {
                bottom: true,
                bottomLeft: false,
                bottomRight: false,
                left: false,
                right: false,
                top: true,
                topLeft: false,
                topRight: false,
              }}
            >
bokuweb commented 5 years ago

@techromantic Is this same issue?? https://github.com/bokuweb/react-rnd/issues/498

techromantic commented 5 years ago

I looked at that issue as well. At face value it looks like the exact same error caused by draggable but I'm using the latest version after the commit, I thought it would have fixed mine.

techromantic commented 5 years ago

@jamespfarrell - we had a really similar issue - could you help?

ng: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
    in Draggable (created by Rnd)
    in Rnd (at dynamicCalendarItem.js:100)
    in DynamicCalendarItem (at calendarEntries.js:78)

function.console.(anonymous function) | @ | index.js:1446
-- | -- | --
  | warningWithoutStack | @ | react-dom.development.js:520
  | warnAboutUpdateOnUnmounted | @ | react-dom.development.js:19200
  | scheduleWork | @ | react-dom.development.js:20687
  | enqueueSetState | @ | react-dom.development.js:11568
  | Component.setState | @ | react.development.js:336
  | Draggable._this.onDragStop | @ | react-draggable.js:2562
  | DraggableCore._this.handleDragStop | @ | react-draggable.js:2244

place in draggable where it occurs draggableCore

   _this.setState({
          dragging: false,
          lastX: NaN,
          lastY: NaN
        }); // Call event handler

        _this.props.onStop(e, coreEvent);

draggable

      _this.onDragStop = function (e, coreData) {
        if (!_this.state.dragging) return false; // Short-circuit if user's callback killed it.

        var shouldStop = _this.props.onStop(e, createDraggableData(_this, coreData));

        if (shouldStop === false) return false;
        var newState
        /*: $Shape<DraggableState>*/
        = {
          dragging: false,
          slackX: 0,
          slackY: 0
        }; // If this is a controlled component, the result of this operation will be to
        // revert back to the old position. We expect a handler on `onDragStop`, at the least.

        var controlled = Boolean(_this.props.position);

        if (controlled) {
          var _this$props$position = _this.props.position,
              _x2 = _this$props$position.x,
              _y2 = _this$props$position.y;
          newState.x = _x2;
          newState.y = _y2;
        }

        _this.setState(newState);
      };
techromantic commented 5 years ago

@bokuweb - i apologize, all of my issues were being caused by a key issue. when giving rnd components keys, if using position (y position in my case) as a key, rendering a list of components with a matching y will cause the moved component to lose state and props. solved by making the key the y position plus a random value