react dndでドラッグアンドドロップを作る！

# はじめに
現場でフォームのドラッグアンドドロップを検証する機会があり、Reactのdndライブラリを使ってみたので、共有します！

# インストール

以下の公式ドキュメントに記載の通り、インストールを進めます。
```
npm install react-dnd react-dnd-html5-backend
```

https://react-dnd.github.io/react-dnd/about

# 実装コード全体
以下のコードで基本的にドラッグアンドドロップを実装することができます。


```tsx:
import React, { useState } from 'react';

interface Item {
  id: number;
  text: string;
}

const DragDropExample = () => {
  const [items, setItems] = useState<Item[]>([
    { id: 1, text: 'アイテム1' },
    { id: 2, text: 'アイテム2' },
    { id: 3, text: 'アイテム3' }
  ]);

  const handleDragStart = (e: React.DragEvent<HTMLDivElement>, id: number) => {
    e.dataTransfer.setData('text/plain', id.toString());
  };

  const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault();
  };

  const handleDrop = (e: React.DragEvent<HTMLDivElement>, targetId: number) => {
    e.preventDefault();
    const draggedId = parseInt(e.dataTransfer.getData('text/plain'));

    if (draggedId === targetId) return;

    const newItems = [...items];
    const draggedItem = newItems.find(item => item.id === draggedId);

    // draggedItemのnullチェックを追加
    if (!draggedItem) {
      console.error('ドラッグされたアイテムが見つかりません');
      return;
    }

    const targetIndex = newItems.findIndex(item => item.id === targetId);
    if (targetIndex === -1) {
      console.error('ターゲットアイテムが見つかりません');
      return;
    }

    // 元の位置から削除
    newItems.splice(newItems.findIndex(item => item.id === draggedId), 1);
    // 新しい位置に挿入
    newItems.splice(targetIndex, 0, draggedItem);

    setItems(newItems);
  };

  return (
    <div className="p-4">
      <h2 className="text-xl font-bold mb-4">ドラッグ&ドロップ例</h2>
      <div className="space-y-2">
        {items.map(item => (
          <div
            key={item.id}
            draggable
            onDragStart={(e) => handleDragStart(e, item.id)}
            onDragOver={handleDragOver}
            onDrop={(e) => handleDrop(e, item.id)}
            className="p-4 bg-white border border-gray-200 rounded shadow cursor-move hover:bg-gray-50"
          >
            {item.text}
          </div>
        ))}
      </div>
    </div>
  );
};

export default DragDropExample;


```

# 詳細説明
今回はTypescriptで書いているので、interfaceをセットします。
ドラッグアンドドロップではidが大切な要素になっており、このidを渡して要素の位置を変更していきます。


```
interface Item {
  id: number;    // 各アイテムを識別するためのID
  text: string;  // 表示するテキスト
}
```

### 初期値

useStateフックを使用して、基本的な要素を設定します。
今回は初期値として、以下の3アイテムをセットしました。
```
const [items, setItems] = useState<Item[]>([
  { id: 1, text: 'アイテム1' },
  { id: 2, text: 'アイテム2' },
  { id: 3, text: 'アイテム3' }
]);
```
### ドラッグ開始の処理
ドラッグを開始したときに、そのアイテムのIDをdataTransferオブジェクトに保存します。

```
const handleDragStart = (e: React.DragEvent<HTMLDivElement>, id: number) => {
  e.dataTransfer.setData('text/plain', id.toString());
};
```

### ドラッグオーバーの処理
デフォルトのドラッグオーバー動作を防ぐために必要で、これがないとドロップが機能しません。
```
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
  e.preventDefault();
};
```

### ドロップの処理
```tsx
const handleDrop = (e: React.DragEvent<HTMLDivElement>, targetId: number) => {
  e.preventDefault();
  // ドラッグされたアイテムのIDを取得
  const draggedId = parseInt(e.dataTransfer.getData('text/plain'));
  
  // 同じアイテムへのドロップは無視
  if (draggedId === targetId) return;

  // 配列のコピーを作成
  const newItems = [...items];
  const draggedItem = newItems.find(item => item.id === draggedId);
  
  // エラーチェック
  if (!draggedItem) {
    console.error('ドラッグされたアイテムが見つかりません');
    return;
  }

  const targetIndex = newItems.findIndex(item => item.id === targetId);
  if (targetIndex === -1) {
    console.error('ターゲットアイテムが見つかりません');
    return;
  }
  
  // アイテムの位置を更新
  newItems.splice(newItems.findIndex(item => item.id === draggedId), 1);
  newItems.splice(targetIndex, 0, draggedItem);
  
  // 状態を更新
  setItems(newItems);
};
```

### UI実装
最後に以下のUIを実装して完了です。
```tsx
return (
  <div className="p-4">
    <h2 className="text-xl font-bold mb-4">ドラッグ&ドロップ例</h2>
    <div className="space-y-2">
      {items.map(item => (
        <div
          key={item.id}
          draggable           // ドラッグ可能にする
          onDragStart={(e) => handleDragStart(e, item.id)}
          onDragOver={handleDragOver}
          onDrop={(e) => handleDrop(e, item.id)}
          className="p-4 bg-white border border-gray-200 rounded shadow cursor-move hover:bg-gray-50"
        >
          {item.text}
        </div>
      ))}
    </div>
  </div>
);
```

# おわりに
ドラッグアンドドロップはいくつか方法があるようなのですが、Reactを使用している場合はこのライブラリを使うことで比較的簡単に実装できることがわかりました。
参考になれば幸いです！

# JISOUのメンバー募集中！

プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
実践的なカリキュラムで、あなたのエンジニアとしてのキャリアを最短で飛躍させましょう！
興味のある方は、ぜひホームページからお気軽にカウンセリングをお申し込みください！
▼▼▼

https://projisou.jp/