atlassian / react-beautiful-dnd

Beautiful and accessible drag and drop for lists with React
https://react-beautiful-dnd.netlify.app
Other
33.4k stars 2.57k forks source link

Nested RBD and React-Window Virtualization #1628

Open ghost opened 4 years ago

ghost commented 4 years ago

CONTEXT

I have a tree data structure that, if I flatten the tree into a linear list, can use RBD with react-window virtualization for a very performant drag and drop where all nodes are equal.

But desiring to support drag and drop for a hierarchy of a node and all its children in a single drag, I implemented a nested RBD solution for the tree similar to your example code: https://react-beautiful-dnd.netlify.com/?path=/story/complex-vertical-list--nested-vertical-lists. It works well but is sluggish with large lists. As per your example, this nested solution does not use react-window.

QUESTION

Is it possible to have a nested RBD solution with react-window virtualization? Or are they inherently incompatible, the one requiring nesting and the other requiring a flat linear list?

Thank you.

switz commented 4 years ago

I implemented a nested RBD that works great. I have 3 layers, and can drag across each layer quite well. However, my outermost layer is quite large and therefore I implemented react-window (virtualization) onto it. It seems to work quite well, except a nested layer when dragging seems to be offset by several hundred pixels (e.g. 500 px to the left and down) from where the cursor actually is. Any ideas?

edit: using the renderClone api for each of the nested Droppables actually fixes it. The console complains that there's no placeholder, but it seems to work and the API docs say you can use renderClone even without the [nested] lists being virtualized. 👌

edit2: for any future intrepidors (is that a word?), I was able to maintain my memoization on the components by adding a dontMemoize prop to the renderClone and checking the transform on the provided.draggableProps such as:

export default React.memo(Picture, (prevProps, nextProps) => {
  if (prevProps.dontMemoize || nextProps.dontMemoize) return false;

  return (
    prevProps.provided.draggableProps.style.transform ===
      nextProps.provided.draggableProps.style.transform &&
    prevProps.id === nextProps.id // or whatever memoization check you have
  );
});
ghost commented 4 years ago

Interesting. So you don't and can't virtualize the whole nested RBD but can virtualize a single nesting layer. Well, better than no virtualization.

Any possibility of you throwing the essence of your solution into a code sandbox? Thanks.

switz commented 4 years ago

This is basically the same thing: https://codesandbox.io/s/simple-virtual-list-board-vgvzt

The only difference is the virtualization is on the inside layer. If you move it up to the top layer, just replace the {provided.placeholder} api with a renderClone (now being used in both layers) and you should be good to go. For any further nesting, just keep using renderClone over placeholder. Let me know if that makes sense.

JamesGelok commented 4 years ago

@switz

I tried doing what you mentioned, but I'm running into a bit of an error trying to move items from one list to another.

https://codesandbox.io/s/wip-outerlist-uses-window-scroller-5hcbn?file=/src/index.js

Used the codesandbox link you posted above as a starting point, and tried to make changes until I could get something working.

I added helper functions that did the virtualization for columns just like it was doing for items.

"Base JSX"      : Item              -> Col
"Draggable Box" : Row               -> Column
"Renderer"      : makeRowRenderer   -> makeColumnRenderer
"DropVirtList"  : ItemList          -> ColList

But when I try to drag one item to another list and scroll down with it, it gives an error: Invariant failed: Cannot find droppable entry with id [column-4]

Are you able to tell what's going wrong?

switz commented 4 years ago

I'm not sure exactly what's happening but if you log out the column.id in ItemList, I'm seeing it get reset to the root columns for some reason on occasion:

image

Even though it should be the columns in the view. I'd play around with it and figure out why that's happening, clearly the ListItem Droppables are not being rendered properly, perhaps has to do with your memo functions (which don't seem to have any special checks, like checking if column.id has changed).