clauderic / dnd-kit

The modern, lightweight, performant, accessible and extensible drag & drop toolkit for React.
http://dndkit.com
MIT License
12.91k stars 640 forks source link

Unable to Drop Items in Empty Column on Larger Screens #1451

Open raunaksaral opened 3 months ago

raunaksaral commented 3 months ago

Description: I am experiencing an issue where items cannot be dropped into an empty column when using a larger screen size. The problem does not occur every time, and I have recorded a Loom video demonstrating the issue.

Steps to Reproduce: On a larger screen, attempt to drag and drop an item into an empty column. Observe if the item is successfully dropped or if any errors occur. Note that the issue does not happen consistently. Expected Behavior: Items should be able to be dropped into empty columns regardless of screen size.

Actual Behavior: Items fail to drop into empty columns on larger screens. The issue occurs intermittently and is not present on all screen sizes.

Code Sample: Here’s a simplified version of the code related to the problem:

const RelationshipBoard = ({ isLoading, isLoadingMoreId, onLaneEnd, changeColumn }) => {
  const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 0.3 } }));
  const { columnData: serverColumnData } = useSelector((state) => state.relationship);
  const [activeData, setActiveData] = useState(null);
  const [columnData, setColumnData] = useState(() => (serverColumnData?.lanes ? [...serverColumnData?.lanes] : []));

  useEffect(() => {
    setColumnData(() => (serverColumnData?.lanes ? [...serverColumnData?.lanes] : []));
  }, [serverColumnData]);

  const dropAnimation = {
    ...defaultDropAnimation,
  };

  const handleDragStart = ({ active }) => {
    const activeCard = active?.data?.current?.card;
    const _activeColId = active?.data?.current?.columnId;
    setActiveData({ card: activeCard, columnId: _activeColId });
  };

  const handleDragOver = ({ active, over }) => {
    const _activeColId = active?.data?.current?.columnId;
    const _overColId = over?.data?.current?.columnId || over?.id;

    const activeColumn = getColumnById(columnData, _activeColId);
    const overColumn = getColumnById(columnData, _overColId);

    if (!activeColumn || !overColumn || active?.id === over?.id) {
      return;
    }
    setColumnData((existingColumnData) => {
      const activeItems = activeColumn?.cards;
      const overItems = overColumn?.cards;
      const activeIndex = activeItems.findIndex((item) => item.id === active.id);
      const overIndex = overItems.findIndex((item) => item.id === over?.id);
      return existingColumnData.map((column) => {
        let cards = column.cards;
        let count = column.count;
        if (column.id === activeColumn.id) {
          cards = [...column.cards.filter((item) => item.id !== active.id)];
          count -= 1;
        }
        if (column.id === overColumn?.id) {
          cards = [
            ...cards.slice(0, overIndex),
            activeItems[activeIndex],
            ...cards.slice(overIndex, column.cards.length),
          ];
          count += 1;
        }
        return { ...column, cards, count };
      });
    });
  };
  const handleDragEnd = ({ active, over }) => {
    const _activeColId = active?.data?.current?.columnId;
    const _overColId = over?.data?.current?.columnId || over?.id;

    const activeColumn = getColumnById(columnData, _activeColId);
    const overColumn = getColumnById(columnData, _overColId);

    if (!activeColumn || !overColumn) {
      return;
    }
    const activeItems = activeColumn?.cards;
    const overItems = overColumn?.cards;
    // Find the indexes for the items
    const activeIndex = activeItems.findIndex((item) => item.id === active.id);
    const overIndex = overItems.findIndex((item) => item.id === over?.id);
    if (activeIndex !== overIndex) {
      setColumnData((existingColumnData) => {
        return existingColumnData.map((column) => {
          if (column.id === overColumn.id) {
            return {
              ...column,
              cards: arrayMove(column.cards, activeIndex, overIndex),
            };
          }
          return column;
        });
      });
    }
    changeColumn(activeData.card.id, activeData.columnId, overColumn.id, overIndex, activeData.card);
    setActiveData(null);
  };

  return (
    <DndContext
      sensors={sensors}
      collisionDetection={closestCorners}
      onDragStart={handleDragStart}
      onDragOver={handleDragOver}
      onDragEnd={handleDragEnd}>
      <div className="relationship-board-container">
        {isLoading
          ? loadingData.map((column) => (
              <BoardColumn key={column.id} column={column} isLoadingMoreId={isLoadingMoreId} onLaneEnd={onLaneEnd} />
            ))
          : columnData?.map((column) => (
              <BoardColumn key={column.id} column={column} isLoadingMoreId={isLoadingMoreId} onLaneEnd={onLaneEnd} />
            ))}
      </div>
      <DragOverlay dropAnimation={dropAnimation}>
        {activeData?.card ? <CustomCard {...activeData.card} /> : null}
      </DragOverlay>
    </DndContext>
  );
};
const BoardColumn = ({ column, isLoadingMoreId, onLaneEnd }) => {
  const { setNodeRef } = useDroppable({
    id: column.id,
  });

  const containerRef = useRef(null);

  const handleScroll = () => {
    const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
    if (
      Math.round(scrollHeight - scrollTop) > clientHeight - 10 &&
      Math.round(scrollHeight - scrollTop) < clientHeight + 10
    ) {
      onLaneEnd(column.id);
    }
  };

  return (
    <div className="relationship-board-column">
      <LaneHeader title={column.title} count={column.count} />
      <SortableContext id={column.id} items={column.cards} strategy={verticalListSortingStrategy}>
        <div
          className="relationship-board-card-container"
          ref={(ref) => {
            setNodeRef(ref);
            containerRef.current = ref;
          }}
          onScroll={handleScroll}>
          {column?.cards?.map((card, index) => (
            <SortableDndCard key={card.id} card={card} columnId={column.id} idx={index} />
          ))}
        </div>
      </SortableContext>
      {Boolean(isLoadingMoreId === column.id) && (
        <div className="relationship-board-loader-container">
          <ThreeDotsLoader color="var(--purple-p-400)" />
        </div>
      )}
    </div>
  );
};

const SortableDndCard = ({ card, columnId, idx }) => {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
    id: card.id,
    data: { columnId, card },
  });

  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0 : 1,
  };

  return (
    <div ref={setNodeRef} style={style} {...attributes} {...listeners}>
      <CustomCard {...card} columnId={columnId} />
    </div>
  );
};

Loom Video: Video1 Video2

I’m not sure if there’s something wrong with the way the implementation is set up, particularly with respect to how items are handled when dropped into an empty column. Any guidance or suggestions on what might be causing this issue or how to resolve it would be greatly appreciated.

danilosnDesk commented 3 months ago

Its because the over id is getting null when over the void column, fix it by doing. if (!overColumn && over?.id && column[over?.id]) overColumn = over?.id; }. Hope helped!

Consider see it for more:https://github.com/danilosnDesk/dnd_kit_multiple_container_with_mui