これはなに
React のドラッグ&ドロップライブラリである dnd kit
を使用する際に、
a タグや button タグの要素をソート可能にしつつ、クリック時の遷移やイベント発火をさせられるようにする方法の備忘録です。
デフォルトの挙動
公式サイトのドキュメントのコードにスタイルやクリックイベント等を追加したものです。
import React, { useState } from "react";
import {
closestCenter,
DndContext,
DragOverlay
} from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
verticalListSortingStrategy
} from "@dnd-kit/sortable";
import { SortableItem } from "./SortableItem";
import { Item } from "./Item";
export default function App() {
const [activeId, setActiveId] = useState(null);
const [items, setItems] = useState(["1", "2", "3"]);
return (
<DndContext
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{items.map((id) => (
<SortableItem key={id} id={id} />
))}
</SortableContext>
<DragOverlay>{activeId ? <Item id={activeId} /> : null}</DragOverlay>
</DndContext>
);
function handleDragStart(event) {
const { active } = event;
setActiveId(active.id);
}
function handleDragEnd(event) {
const { active, over } = event;
if (active.id !== over.id) {
setItems((items) => {
const oldIndex = items.indexOf(active.id);
const newIndex = items.indexOf(over.id);
return arrayMove(items, oldIndex, newIndex);
});
}
setActiveId(null);
}
}
import React, { useState } from "react";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
export function SortableItem(props) {
const [clicked, setClicked] = useState<boolean>(false);
const {
attributes,
listeners,
setNodeRef,
transform,
transition
} = useSortable({ id: props.id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
backgroundColor: clicked ? "pink" : "skyblue",
border: "1px solid rgba(0, 0, 0, 0.12)",
cursor: "grab",
display: "block",
fontSize: 16,
padding: 4,
textAlign: "center",
width: 150
};
return (
<button
onClick={() => setClicked((prevState) => !prevState)}
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
>
{props.id}
{clicked && " : clicked"}
</button>
);
}
import React, { forwardRef } from "react";
export const Item = forwardRef(({ id, ...props }, ref) => {
return (
<div {...props} ref={ref} style={style}>
{id}
</div>
);
});
const style = {
backgroundColor: "lightgreen",
border: "1px solid rgba(0, 0, 0, 0.12)",
cursor: "grab",
display: "flex",
flexDirection: "column",
opacity: 0.8,
textAlign: "center",
width: 150
};
a タグや button タグの要素をソートできるようにした場合、
デフォルトではその要素をクリックしてもソートの処理が優先されて、
本来クリック時に起きるリンク先への遷移やイベントが起こらないようになっています。
上記コードでは要素をクリックした際に、
背景色をピンクにして要素内の文字列に変化が起きるように onClick イベントを仕込んでいますが、
下の画像のようにソート処理が優先されているのがわかります。
クリック時のイベントを動くようにする
sensors
を利用することで実現ができます。
sensors
は、様々な入力デバイスからドラッグイベントの発生を感知するための仕組みで、
今回の例ではマウスからの入力を感知することができる MouseSensor
を使用します。
import React, { useState } from "react";
import {
closestCenter,
DndContext,
DragOverlay,
useSensor, // Add!!
useSensors, // Add!!
MouseSensor // Add!!
} from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
verticalListSortingStrategy
} from "@dnd-kit/sortable";
import { SortableItem } from "./SortableItem";
import { Item } from "./Item";
export default function App() {
const [activeId, setActiveId] = useState(null);
const [items, setItems] = useState(["1", "2", "3"]);
const sensors = useSensors( // Add!!
useSensor(MouseSensor, { activationConstraint: { distance: 5 } }) // Add!!
); // Add!!
return (
<DndContext
sensors={sensors} // Add!!
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
@dnd-kit/core
から新たに、
useSensor
useSensors
MouseSensor
をimport するようにしました。
そしてそれらを用いた以下のコードを追加しています。
const sensors = useSensors(
useSensor(MouseSensor, { activationConstraint: { distance: 5 } })
);
マウスを用いてソート可能要素をクリックした際、そのまま 5px 以上ドラッグしないと
ソート処理が動かないように設定しています。
それにより、本来のクリック時のイベントが動作するようになります。