14
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

お題は不問!Qiita Engineer Festa 2023で記事投稿!

[dnd kit]ソート可能な要素のクリックイベントを動くようにする

Last updated at Posted at 2023-07-06

これはなに

React のドラッグ&ドロップライブラリである dnd kit を使用する際に、
a タグや button タグの要素をソート可能にしつつ、クリック時の遷移やイベント発火をさせられるようにする方法の備忘録です。

デフォルトの挙動

公式サイトのドキュメントのコードにスタイルやクリックイベント等を追加したものです。

App.tsx
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);
  }
}
SortableItem.tsx
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>
  );
}
Item.tsx
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 イベントを仕込んでいますが、
下の画像のようにソート処理が優先されているのがわかります。

dnd_before.gif

クリック時のイベントを動くようにする

sensors を利用することで実現ができます。
sensors は、様々な入力デバイスからドラッグイベントの発生を感知するための仕組みで、
今回の例ではマウスからの入力を感知することができる MouseSensor を使用します。

App.tsx
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 以上ドラッグしないと
ソート処理が動かないように設定しています。
それにより、本来のクリック時のイベントが動作するようになります。

dnd_after.gif

14
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?