UGA Boxxx

つぶやきの延長のつもりで、知ったこと思ったこと書いてます

【dnd-kit】リスト間ドラッグ&ドロップの実装

dnd-kit を使って複数のリスト間でドラッグ&ドロップを実現する場合のメモ

複数リストで考慮すべきポイント

  1. 各リストごとに SortableContext を用意する
  2. アイテムの id がリスト間で重複しないように設計する
  3. onDragEnd で「どのリストからどのリストへ移動したのか」を判定する
  4. 並び替えだけでなく、リスト間移動も考慮した状態管理を行う

プレフィックス付きID設計

リスト間でアイテムの id が重複する可能性がある場合、リストごとにプレフィックスを付与することで管理しやすくなる

重複していると反応しないので、つけておいた方が良い

実装例

<DndContext onDragEnd={handleDragEnd}>
  <div style={{ display: 'flex', gap: '20px' }}>
    {lists.map(list => (
      <SortableContext key={list.id} items={list.items} strategy={verticalListSortingStrategy}>
        <div>
          <h3>{list.title}</h3>
          {list.items.map(itemId => (
            <SortableItem key={itemId} id={itemId} />
          ))}
        </div>
      </SortableContext>
    ))}
  </div>
</DndContext>

onDragEnd の実装ポイント

リスト間でアイテムが移動した場合、以下のように判定する

const handleDragEnd = (event) => {
  const { active, over } = event;
  if (!over) return;

  const fromList = findListByItemId(active.id);
  const toList = findListByItemId(over.id);

  if (!fromList || !toList) return;

  if (fromList.id === toList.id) {
    // 同一リスト内の並び替え
    reorderWithinList(fromList, active.id, over.id);
  } else {
    // リスト間移動
    moveItemToAnotherList(fromList, toList, active.id, over.id);
  }
};

状態管理の例

const [lists, setLists] = useState([ { id: 'list-a', title: 'リストA', items: ['a-1', 'a-2'] }, { id: 'list-b', title: 'リストB', items: ['b-1'] }, ]);

リストごとにアイテム配列を保持し、移動時に適切に更新する

よくある課題と対応策

課題 対応策
ID 重複によるバグ プレフィックスを付与する
空のリストにドロップできない リスト全体に useDroppable を適用
ドロップ先の判定が曖昧になる collisionDetection を調整する
ドラッグ中の見た目が崩れる DragOverlay を適切に設定する