ドラッグ&ドロップでリスト並び替え!@dnd-kitを使った実装の工夫

に公開

💡 導入の小話: とある会社のちょっとしたやりとりから…

👤 A さん:「この順番ってどこで変えるんでしたっけ?」
👤 B さん:「えーと、編集画面で index の数字を直接書き換えてください」
👤 A さん:「(げ…10 個もあるのに手で?)」
👤 B さん:「はい、1 から順番に振り直せば OK です。かぶると保存できませんけど」
👤 A さん:「あっ…(それ一番やっちゃいけないやつ…)」


社内ツールや CMS でよくある「リストの並び順を番号で管理する UI」は、最初はシンプルですが、
項目数が増えるにつれてメンテナンスが地獄化します。

今回は、このような不便を解消するために導入した、@dnd-kit を使った「ドラッグ&ドロップによる並び替え」機能の実装について紹介します。


この記事では、初心者なりに「こうすると意外とうまくいった!」というポイントや、
実装時に工夫した点を紹介しています。
同じような機能を実装しようとしている方の参考になればうれしいです!


1. 並び替え用のカスタムフックを作った!

順序変更のロジックを useSortableList にまとめました。

const [list, setList] = useState(initialList);

const handleSort = (from: number, to: number) => {
	const updated = [...list];
	const [moved] = updated.splice(from, 1);
	updated.splice(to, 0, moved);
	setList(updated);
};

配列の並びを変える処理と、元の状態との比較もできるようにして、保存ボタンを出すかどうか判断できるようにしました。

フックを作成しておくと、いろいろなリストで使えて便利です!


2. クリックとドラッグをちゃんと区別した!

ドラッグとクリックが混ざらないよう、移動量が小さければクリックと判断するようにしました。

if (Math.abs(dx) + Math.abs(dy) < 5) {
	onClick();
} else {
	handleSort(from, to);
}

これで、間違えてページ遷移してしまうことがなくなりました!


3. ドラッグ中の見た目もわかりやすく!

ドラッグしている要素だけ見た目を変えるようにしました。

const style = {
	opacity: dragging ? 0.5 : 1,
	borderColor: dragging ? "blue" : "gray",
};

ちょっとした工夫ですが、操作感がよくなります!
アニメーションを付けると、デザインが更によくなりました。


4. バックエンドでも安全に処理!

まとめて送る更新リストの中に重複がないか、保存前にチェックしています。

const ids = updates.map((u) => u.id);
if (new Set(ids).size !== ids.length) throw Error();

さらにトランザクションを使って、全件一括で保存するようにしました。途中で失敗しても安心です!


5. 保存とキャンセル機能もつけた!

変更がなかったら保存しないようにし、戻せるようにキャンセル機能もつけました。

if (!hasChanged) return;

await saveIndexes(list);

まとめ

  • useSortableList で並び替え処理を共通化
  • クリックとドラッグの誤操作を防止
  • 見た目の工夫で操作性アップ
  • バリデーションとトランザクションで安心保存
  • 保存・キャンセルの UI でやり直しも簡単に

「地味だけど便利になる工夫」をぎゅっと詰めた機能でした!

読んでくださってありがとうございました 😊

Discussion