当サイトは、アフィリエイト広告を利用しています
ReactのGatsby製ブログでタグ選択で記事をフィルタリングしたいと思い、
タグ選択時にドラックアンドドロップできないかと考え、色々探してみたら
あったので実装方法をまとめておく。
React Draggable Tagsを使うことで実現できた
yarn add react-draggable-tags
React Draggable Tagsの詳細はこちらを参照
ここにある「Cross-Area Drag:」を参考にして
サンプルを実装する。
下記に全体のソースを載せる。
サンプルの動きとしては、左のタグエリアから右のタグエリアに
ドラッグアンドドロップでタグを移動できるようにする。
※その際、それぞれの配列にタグが追加される
またスタイリングはCSS in JSのEmotinonを使う
/** @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]}><DraggableArea1css={[flexItem]}tags={left}onChange={(left) => {setLeft(left);}}render={({ tag }) => <button>{tag.content}</button>}/></div>{/* rightエリア */}<div css={[gridItem]}><DraggableArea2css={[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;`];
上記のサンプルソースを解説する。
react-draggable-tagsのDraggableAreasGroupを使ってgroupオブジェクトを作成し、 タグを入れるDraggableAreaコンポーネントを作成する
~/** @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が必要と記載があり、
またない場合、ドラッグが正常に動かない。
~// 初期タグ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コンポーネントにタグのオブジェクト配列をpropsで渡し、
ドラックアンドドロップエリアをrenderさせる
~return (<div css={[grid]}>{/* leftエリア */}<div css={[gridItem]}><DraggableArea1css={[flexItem]}tags={left}onChange={(left) => {setLeft(left);}}render={({ tag }) => <button>{tag.content}</button>}/></div>{/* rightエリア */}<div css={[gridItem]}><DraggableArea2css={[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コンポーネントの使い方について
まとめる。
基本的には下記の三つをpropsとして渡す必要がある
tagsには表示するタグの配列を渡す。この配列を元にrenderでタグが画面に描画される。
ドラッグアンドドロップ操作後の配列を受け取り、要素をrenderする。 動作をデバッグで見てみると、ドラッグアンドドロップした場合、
{/* leftエリア */}<DraggableArea1css={[flexItem]}tags={left}onChange={(left) => {setLeft(left);}}render={({ tag }) => <button>{tag.content}</button>}/>{/* rightエリア */}<DraggableArea2css={[flexItem]}tags={right}onChange={(right) => {setRight(right);}}render={({tag}) => <button>{tag.content}</button>}/>
例をあげると、DraggableArea1(leftエリア)[apple,banana,tomato]から DraggableArea2(rightエリア)[nasubi]にtomatoを ドラッグアンドドロップした場合、
上記のようにドラッグアンドドロップ操作後の配列を元にrenderされるようだ。
ドラッグアンドドロップが行われた場合に、それぞれのstateを更新する。
引数として渡されるのはrenderの時と同じで、ドラッグアンドドロップ操作後の配列が
渡されるため、そのままstateにセットする。
Reactでドラッグアンドドロップする方法は調べるとたくさんでてきたが、ほとんどが
ドラックして動かすものや、テーブル項目の順番をドラックアンドドロップで入れ替えるもの
が多く、タグ選択は見当たらなかったので調べてサンプル実装してみた
ドラッグアンドドロップ操作後の配列が渡されるという点に気づかず詰まってしまった。
サンプルを作った感じだとブログに導入することはできそうなので、後日実装してみる。
最後に動作確認できるようにcodesandboxに上記ソースを残しておく。