Skip to content
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

"Cannot announce to unmounted node" error with state update in onDragEnd #556

Closed
hspens opened this issue Jun 19, 2018 · 10 comments
Closed

Comments

@hspens
Copy link

hspens commented Jun 19, 2018

Bug or feature request?

Bug

Expected behavior

No errors and no component unmounting.

Actual behavior

The component implementing a DragDropContext unmounts on onDragEnd events.

What version of React are you using?

react@16.4.0

What version of react-beautiful-dnd are you running?

react-beautiful-dnd@7.1.3

What browser are you using?

Version 67.0.3396.87 (Official Build) (64-bit)

Issue tl;dr

I'm implementing a simple horizontal list with user selected items in it as a component in my app. The selected items are stored in the state of the app and are passed down as a prop to the list. When the user reorders the list a callback function is called from within "onDragEnd" to update the application state, i.e. it is the container object and not the simple horizontal list which holds the true state of the ordering of list items. Keeping the state outside the list seems to be an issue though as I'm getting a

Cannot announce to unmounted node

error in Chrome when re-ordering due to the simple horizontal list component being unmounted.

  onDragEnd(result) {
    // dropped outside the list
    if (!result.destination) {
      return;
    }

    const items = reorder(
      this.state.items,
      result.source.index,
      result.destination.index
    );

    // this.setState({
    //   items,
    // });
    this.props.updateFilterOrder(items)
  }

Issue

Firstly, thanks for this awesome library. I must say that I was thrilled when I found it since I was specifically searching for a drag-and-drop library similar to the one used in Atlassian Jira. I'm a complete beginner in React and may have implemented this library in the wrong way but I'm still very confused as to why I'm getting this kind of error.

I want to have a table of data in my app where the user can drag-and-drop both the columns and the rows. I'm implementing this with two DragDropContext-components, one acting as the column defining the rows and one as the row defining the columns.

image

I used the codepen link to on github (https://codesandbox.io/s/k260nyxq9v) to give me a template of the component and created a custom one where I can send in the direction of the list as a prop. I use this "DragableList" in a container-component which holds the state of the application, i.e. data fetched from a server. The user can select columns to display in the report from a static list populated by server data. The selected items are passed down to the component created with react-beautiful-dnd through a prop (selectedKeys). The user can also remove items through the "onKeyDeselect" callback present as an "onClick" on each item in the DragableList component.

<DragableList direction={"horizontal"} 
              selectedKeys={this.state.selectedFilters}
              onKeyDeselect={key => this.onKeyDeselect(key)} 
              updateFilterOrder={filters => this.updateFilterOrder(filters)}/>

DragableList.js

import React, { Component } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';

const reorder = (list, startIndex, endIndex) => {
  const result = Array.from(list);
  const [removed] = result.splice(startIndex, 1);
  result.splice(endIndex, 0, removed);

  return result;
};

const grid = 8;

const getItemStyle = (isDragging, draggableStyle, isHorizontal) => ({
  // some basic styles to make the items look a bit nicer
  userSelect: 'none',
  padding: grid * 1,
  margin: isHorizontal ? `0 ${2}px 0` : `0 0 ${2}px 0`,

  // change background colour if dragging
  background: isDragging ? 'lightgreen' : '#63EB2C',

  // styles we need to apply on draggables
  ...draggableStyle,
});

const getListStyle = (isDraggingOver, isHorizontal) => {
  if (isHorizontal) {
    return ({
      background: isDraggingOver ? "lightblue" : "lightgrey",
      display: "flex",
      padding: 3,
      overflow: "auto",
      marginLeft:"95px"
    })
  } else {
    return ({
      background: isDraggingOver ? 'lightblue' : 'lightgrey',
      padding: 3,
      width: 85,
    })
  }  
};

class DragableList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isHorizontal: true,
      // items: getItems(6),
      items: this.props.selectedKeys ? this.props.selectedKeys : []
    };
    this.onDragEnd = this.onDragEnd.bind(this);
  }

  componentDidMount() {
    this.setState(
      {
        isHorizontal:this.props.direction==="horizontal" ? true : false,
      })
  }

  onDragEnd(result) {
    // dropped outside the list
    if (!result.destination) {
      return;
    }

    const items = reorder(
      this.state.items,
      result.source.index,
      result.destination.index
    );

    // this.setState({
    //   items,
    // });
    this.props.updateFilterOrder(items)
  }

  // Normally you would want to split things out into separate components.
  // But in this example everything is just done in one place for simplicity
  render() {
    return (
      <DragDropContext onDragEnd={this.onDragEnd}>
        <Droppable droppableId="droppable" direction={this.state.isHorizontal ? "horizontal" : "vertical"}>
          {(provided, snapshot) => (
            <div
              ref={provided.innerRef}
              style={getListStyle(snapshot.isDraggingOver, this.state.isHorizontal)}
            >
              {this.state.items.map((item, index) => (
                <Draggable key={item.key} draggableId={item.key} index={index}>
                  {(provided, snapshot) => (
                    <div
                      ref={provided.innerRef}
                      {...provided.draggableProps}
                      {...provided.dragHandleProps}
                      style={getItemStyle(
                        snapshot.isDragging,
                        provided.draggableProps.style,
                        this.state.isHorizontal
                      )}
                    >
                      <i onClick={() => this.props.onKeyDeselect(item)} style={{cursor:'pointer'}}>[ # ]</i> {item.name}
                    </div>
                  )}
                </Draggable>
              ))}
              {provided.placeholder}
            </div>
          )}
        </Droppable>
      </DragDropContext>
    );
  }
}

export default DragableList;

The problem is that when a user alters the order of the items in the list the state in the parent of the "DragableList"-component should update its state and pass it down through the prop with "selectedKeys". But when this happens the DragableList-component seemingly unmounts with a

Cannot announce to unmounted node

error in the console in Chrome. I might be wrong but I thought it would be more proper for the container object to be the true state origin and for the DragableList to set its state to that passed down as a prop by the containe and when a user updates the list in DragableList the state of the container-component alters through a callback.

It works if I update the internal state of the DragableList-component but I need to notify the parent container of that change in order to display the table data differently so I tried to use a callback instead but that introduces the issue.

  onDragEnd(result) {
    // dropped outside the list
    if (!result.destination) {
      return;
    }

    const items = reorder(
      this.state.items,
      result.source.index,
      result.destination.index
    );

    // this.setState({
    //   items,
    // });
    this.props.updateFilterOrder(items)
  }
@alexreardon
Copy link
Collaborator

Interesting. I will try to take a look soon

@alexreardon
Copy link
Collaborator

Are you still experiencing this issue in the latest version? Can I please grab an updated codesandbox which demonstrates the issue?

@klaaz0r
Copy link

klaaz0r commented Sep 2, 2018

I am experiencing the exact same issue, trying to keeping the order from outside the component will result in an error :(

@alexreardon
Copy link
Collaborator

Here is our boilerplate: https://codesandbox.io/s/k260nyxq9v

Without an independent example this is hard to action

@hspens
Copy link
Author

hspens commented Sep 13, 2018

I have since posting the issue refactored my code so I'm not experiencing the problem anymore. I will try to take a look at my old code and share a codesandbox if I have time.

@alexreardon
Copy link
Collaborator

This could happen if you unmount in an onDragEnd. I have changed the behaviour from a throw to a dev warning in next. So it won't crash your app, but it will give you some nice pointers on how to avoid dropping screen reader messages 👍

@Hazantip
Copy link

I have since posting the issue refactored my code so I'm not experiencing the problem anymore. I will try to take a look at my old code and share a codesandbox if I have time.

@hspens did you found what is a diff? I also have the same error.

@fgvenegas
Copy link

I have the same issue when I try to keeping the order from outside the component, In specific when I send a fetch to the backend to update the order, what could be the problem?

@alexreardon
Copy link
Collaborator

Will ship with #838

@alexreardon
Copy link
Collaborator

Closed by #838

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants