当サイトは、アフィリエイト広告を利用しています

【React】ドラック&ドロップでタグ選択をする方法

作成日:2022月06月18日
更新日:2024年01月28日

ReactのGatsby製ブログでタグ選択で記事をフィルタリングしたいと思い、
タグ選択時にドラックアンドドロップできないかと考え、色々探してみたら あったので実装方法をまとめておく。

パッケージのインストール

React Draggable Tagsを使うことで実現できた

yarn
yarn add react-draggable-tags

React Draggable Tagsの詳細はこちらを参照

実装してみる

ここにある「Cross-Area Drag:」を参考にして
サンプルを実装する。

全体ソース

下記に全体のソースを載せる。
サンプルの動きとしては、左のタグエリアから右のタグエリアに
ドラッグアンドドロップでタグを移動できるようにする。
※その際、それぞれの配列にタグが追加される
またスタイリングはCSS in JSのEmotinonを使う

App.jsx
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import "./styles.css";
import { DraggableAreasGroup } from "react-draggable-tags";
import { useEffect, useState } from "react";
export default function App() {
const group = new DraggableAreasGroup();
const DraggableArea1 = group.addArea();
const DraggableArea2 = group.addArea();
// 初期タグ
const leftTags = [
{
id: "apple",
content: "apple"
},
{
id: "banana",
content: "banan"
},
{
id: "nashi",
content: "nashi"
},
{
id: "grape",
content: "grape"
},
{
id: "watermelon",
content: "waterm"
}
];
// エリアのuseStateを作成
const [left, setLeft] = useState(leftTags);
const [right, setRight] = useState([]);
// エリア確認
useEffect(() => {
console.log(`leftArray:${JSON.stringify(left, null, 3)}`);
console.log(`rightArray:${JSON.stringify(right, null, 3)}`);
}, [left, right]);
return (
<div css={[grid]}>
{/* leftエリア */}
<div css={[gridItem]}>
<DraggableArea1
css={[flexItem]}
tags={left}
onChange={(left) => {
setLeft(left);
}}
render={({ tag }) => <button>{tag.content}</button>}
/>
</div>
{/* rightエリア */}
<div css={[gridItem]}>
<DraggableArea2
css={[flexItem]}
tags={right}
onChange={(right) => {
setRight(right);
}}
render={({ tag }) => <button>{tag.content}</button>}
/>
</div>
<div css={[log]}>{`leftArray${JSON.stringify(left, null, 3)}`}</div>
<div css={[log]}>{`rightArray:${JSON.stringify(right, null, 3)}`}</div>
</div>
);
}
// 二つのエリアをgridで作る
const grid = () => [
css`
display: grid;
grid-template-columns: repeat(2, minmax(100px, 1fr));
gap: 5px;
`
];
const gridItem = () => [
css`
padding: 10px;
border: 1px solid black;
`
];
// エリアはFlexで作る(タグを寄せるため)
const flexItem = () => [
css`
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
gap: 3px;
`
];
// 改行させるため
const log = () => [
css`
white-space: pre-wrap;
`
];

ソースの解説

上記のサンプルソースを解説する。

DraggableAreaコンポーネントを作成する

react-draggable-tagsのDraggableAreasGroupを使ってgroupオブジェクトを作成し、 タグを入れるDraggableAreaコンポーネントを作成する

App.jsx
~
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import "./styles.css";
import { DraggableAreasGroup } from "react-draggable-tags";
import { useEffect, useState } from "react";
export default function App() {
const group = new DraggableAreasGroup();
const DraggableArea1 = group.addArea();
const DraggableArea2 = group.addArea();
~

タグオブジェクトリストを作成

ドラッグアンドドロップするタグのオブジェクト配列を作成する。
オブジェクトにはユニークなidを持たせる。
ちょっと理由はわからないがドキュメントにはユニークなidが必要と記載があり、
またない場合、ドラッグが正常に動かない。

App.jsx
~
// 初期タグ
const leftTags = [
{
id: "apple",
content: "apple"
},
{
id: "banana",
content: "banan"
},
{
id: "nashi",
content: "nashi"
},
{
id: "grape",
content: "grape"
},
{
id: "watermelon",
content: "waterm"
}
];
// エリアのuseStateを作成
const [left, setLeft] = useState(leftTags);
const [right, setRight] = useState([]);
~

ドラッグアンドドロップ後に再レンダーさせる必要があるため。 作成したタグのオブジェクト配列をuseStateに設定する

DraggableAreaコンポーネントを使ってドラックアンドドロップエリアをrenderする

DraggableAreaコンポーネントにタグのオブジェクト配列をpropsで渡し、
ドラックアンドドロップエリアをrenderさせる

App.jsx
~
return (
<div css={[grid]}>
{/* leftエリア */}
<div css={[gridItem]}>
<DraggableArea1
css={[flexItem]}
tags={left}
onChange={(left) => {
setLeft(left);
}}
render={({ tag }) => <button>{tag.content}</button>}
/>
</div>
{/* rightエリア */}
<div css={[gridItem]}>
<DraggableArea2
css={[flexItem]}
tags={right}
onChange={(right) => {
setRight(right);
}}
render={({ tag }) => <button>{tag.content}</button>}
/>
</div>
<div css={[log]}>{`leftArray${JSON.stringify(left, null, 3)}`}</div>
<div css={[log]}>{`rightArray:${JSON.stringify(right, null, 3)}`}</div>
</div>
);
~

これで左と右のエリアでタグをドラッグアンドドロップで移動することができ、またその結果は
それぞれの配列(useStateにセットしたleftとright)に格納されるようになる。

DraggableAreaコンポーネントの使い方

ドラックアンドドロップエリアを作るに使うDraggableAreaコンポーネントの使い方について
まとめる。
基本的には下記の三つをpropsとして渡す必要がある

tags

tagsには表示するタグの配列を渡す。この配列を元にrenderでタグが画面に描画される。

render

ドラッグアンドドロップ操作後の配列を受け取り、要素をrenderする。 動作をデバッグで見てみると、ドラッグアンドドロップした場合、  

  • ドラッグアンドドロップで追加したタグを含めた配列をループしてrenderしている
  • 引数にはオブジェクトが渡されており、オブジェクト.tagがタグ情報(idやcontent)を保持している
    ※そのため分割代入している
App.jsx(render部分)
{/* leftエリア */}
<DraggableArea1
css={[flexItem]}
tags={left}
onChange={(left) => {
setLeft(left);
}}
render={({ tag }) => <button>{tag.content}</button>}
/>
{/* rightエリア */}
<DraggableArea2
css={[flexItem]}
tags={right}
onChange={(right) => {
setRight(right);
}}
render={({tag}) => <button>{tag.content}</button>}
/>

例をあげると、DraggableArea1(leftエリア)[apple,banana,tomato]から DraggableArea2(rightエリア)[nasubi]tomatoを ドラッグアンドドロップした場合、  

  • DraggableArea1(左エリア)のrenderでループされる配列→[apple,banana]
  • DraggableArea1(右エリア)のrenderでループされる配列→[nasubi,tomato]

上記のようにドラッグアンドドロップ操作後の配列を元にrenderされるようだ。

onChange

ドラッグアンドドロップが行われた場合に、それぞれのstateを更新する。
引数として渡されるのはrenderの時と同じで、ドラッグアンドドロップ操作後の配列が 渡されるため、そのままstateにセットする。

まとめ

Reactでドラッグアンドドロップする方法は調べるとたくさんでてきたが、ほとんどが
ドラックして動かすものや、テーブル項目の順番をドラックアンドドロップで入れ替えるもの
が多く、タグ選択は見当たらなかったので調べてサンプル実装してみた ドラッグアンドドロップ操作後の配列が渡されるという点に気づかず詰まってしまった。
サンプルを作った感じだとブログに導入することはできそうなので、後日実装してみる。

codesandboxで動作確認

最後に動作確認できるようにcodesandboxに上記ソースを残しておく。

React drug & drop select tag Sample

参考

新着記事

タグ別一覧
top