Skip to main content

React Beautiful DND

The example below integrates React Virtuoso with the maintained fork of React Beautiful DND, hello-pangea/dnd. The example works with Beautiful DND too, but causes warnings with StrictMode.

Live Editor
// Generate our initial big data set
const initial = Array.from({ length: 1000 }, (_, k) => ({
  id: `id:${k}`,
  text: `item ${k}`
}));

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

  return result;
}

function Item({ provided, item, isDragging }) {
  return (
    <div style={{ paddingBottom: "8px" }}>
      <div
        {...provided.draggableProps}
        {...provided.dragHandleProps}
        ref={provided.innerRef}
        style={provided.draggableProps.style}
        className={`item ${isDragging ? "is-dragging" : ""}`}
      >
        {item.text}
      </div>
    </div>
  );
}

function App() {
  const [items, setItems] = useState(initial)

  const onDragEnd = React.useCallback(
    (result) => {
      if (!result.destination) {
        return
      }
      if (result.source.index === result.destination.index) {
        return
      }

      // void setItems
      setItems((items) => reorder(items, result.source.index, result.destination.index))
    },
    [setItems]
  )

  const HeightPreservingItem = React.useCallback(({ children, ...props }) => {
    const [size, setSize] = useState(0);
    const knownSize = props["data-known-size"];
    useEffect(() => {
      setSize((prevSize) => {
        return knownSize == 0 ? prevSize : knownSize;
      });
    }, [knownSize]);
    return (
      <div
        {...props}
        className="height-preserving-container"
        style={{
          "--child-height": `${size}px`
        }}
      >
        {children}
      </div>
    );
  }, []);

  return (
    <div style={{ padding: '1rem' }}>
      <style>
      {`
      .height-preserving-container:empty {
        min-height: calc(var(--child-height));
        box-sizing: border-box;
        }
    `}
        </style>
        <PangeaDND.DragDropContext onDragEnd={onDragEnd}>
          <PangeaDND.Droppable
            droppableId="droppable"
            mode="virtual"
            renderClone={(provided, snapshot, rubric) => (
              <Item provided={provided} isDragging={snapshot.isDragging} item={items[rubric.source.index]} />
            )}
          >
          {(provided) => {
            return (
              <Virtuoso
                useWindowScroll
                components={{
                  Item: HeightPreservingItem
                }}
                scrollerRef={provided.innerRef}
                data={items}
                style={{ width: 300, height: 500 }}
                itemContent={(index, item) => {
                  return (
                    <PangeaDND.Draggable draggableId={item.id} index={index} key={item.id} >
                      {(provided) => ( <Item provided={provided} item={item} isDragging={false} />)}
                    </PangeaDND.Draggable>
                  );
                }}
              />
            );
          }}
        </PangeaDND.Droppable>
      </PangeaDND.DragDropContext>
    </div>
  );
}

render(<App />)
Result
Loading...