Reactでアイテムを絞り込み(マルチフィルター)を行う方法
EC・ショッピングアプリなどで、グループや種類による複数条件での絞り込みをReactで行う方法です。
今回は例として、プロダクト毎にタグをつけ、タグで絞り込みができるようにします。
今回利用するReact HookはuseState()
となります。
動作環境:
- Node.js v18.12.1
- React v18.2.0
元となるリストを生成
今回は、以下のようなリストを作りました。
const DATA = [
{
id: 1,
title: "Enjoy studying English",
tags: [
{
id: "tag1",
title: "English",
slug: "english",
},
{
id: "tag2",
title: "For kids",
slug: "kids",
},
],
},
{
id: 2,
title: "Parlons français",
tags: [
{
id: "tag3",
title: "French",
slug: "french",
},
{ id: "tag2", title: "Kids", slug: "kids" },
],
},
{
id: 3,
title: "Intermediate English",
tags: [
{
id: "tag1",
title: "English",
slug: "english",
},
{
id: "tag4",
title: "Adults",
slug: "adults",
},
],
},
{
id: 4,
title: "How to study French",
tags: [
{
id: "tag3",
title: "French",
slug: "french",
},
{
id: "tag4",
title: "Adults",
slug: "adults",
},
],
},
]
今回は上記のようにハードコーディングしていますが、通常の現場ではNext.jsやGatsby.js等によりすべてのアイテムをmap()
で展開するパターンとなると思います。
このconst DATA
は後ほどreturn
内でmap()
展開するため、key
指定のためのid
を設定しています。
絞り込みフィルターを通したリストを生成
用意したconst DATA
をそのまま表示しても絞り込みはできないため、フィルターを通した「整形可能な配列データ」に変換させます。
今回はタグを利用して絞り込みをするため、"ユーザーによって選択されたタグ"を含むアイテムのみ展開するようにします。
const [filterTags, setFilterTags] = useState([])
const filteredDATA = DATA.filter(node =>
filterTags.length > 0
? filterTags.every(filterTag =>
node.tags.map(tag => tag.slug).includes(filterTag)
)
: DATA
)
何かしらのタグが選択されている場合(= filterTags.length > 0
)では、選択されているタグが含まれるアイテムのみをフィルタリング。タグが一切選択されていない時は、初期状態であるDATA
を返します。
ここでのポイントは、every()
の利用です。選択されたタグを持っているアイテムのみをここで絞り込みます。
参考 Array.prototype.every() - JavaScript | MDN
ここで生成したconst filteredDATA
を、return
内で表示させればいいわけですね。
絞り込みフィルターを通したリストを表示
ここまでで、const DATA
をconst filteredDATA
として整形しなおしました。
このfilteredDATA
を、return
内で展開・表示させます。
return(
<>
<ul>
{filteredDATA.map((node) => (
<li key={node.id}>{node.title}</li>
))}
</ul>
<>
)
この時点ではまだ絞り込み用のチェックボックスはなく、絞り込みはできません。
次に絞り込み用のチェックボックスを作ります。
絞り込み用のタグのチェックボックスを作成
絞り込みを行うため、タグのチェックボックスを作ります。
return (
<>
<div>
<label htmlFor="english">
<input
type="checkbox"
onChange={filterHandler}
value="english"
id="english"
/>
<span>English</span>
</label>
<label htmlFor="french">
<input
type="checkbox"
onChange={filterHandler}
value="french"
id="french"
/>
<span>French</span>
</label>
<label htmlFor="kids">
<input
type="checkbox"
onChange={filterHandler}
value="kids"
id="kids"
/>
<span>Kids</span>
</label>
<label htmlFor="adults">
<input
type="checkbox"
onChange={filterHandler}
value="adults"
id="adults"
/>
<span>Adults</span>
</label>
</div>
<div>
<ul>{/* ... */}</ul>
</div>
</>
)
ここでもハードコーディングしていますが、実際の現場ではやはりタグをmap()
で展開することになると思います。生成方法は適宜アレンジしてください。
それぞれのinput
タグにおいて、チェックが入った時・外れた時に発火するハンドラー(ここではfilterHandler
という関数名で作成)を設定します。ハンドラーはすべて共通です。このハンドラーによって、filterTags
(チェック済みタグのリスト)を逐次書き換えます。
ハンドラ自体は以下のようにしました。
const filterHandler = event => {
if (event.target.checked) {
setFilterTags([...filterTags, event.target.value])
} else {
setFilterTags(
filterTags.filter(filterTag => filterTag !== event.target.value)
)
}
}
ユーザーによってチェックされたらfilterTags
(チェック済みタグのリスト)に当該タグを追加。チェックが外れたらfilterTags
からそのタグを削除。至ってシンプルな内容です。
コードまとめ
すべてのコードをまとめると、以下の通りになります。
import { useState } from "react"
export default function Filter() {
const DATA = [
{
id: 1,
title: "Enjoy studying English",
tags: [
{
id: "tag1",
title: "English",
slug: "english",
},
{
id: "tag2",
title: "For kids",
slug: "kids",
},
],
},
{
id: 2,
title: "Parlons français",
tags: [
{
id: "tag3",
title: "French",
slug: "french",
},
{ id: "tag2", title: "Kids", slug: "kids" },
],
},
{
id: 3,
title: "Intermediate English",
tags: [
{
id: "tag1",
title: "English",
slug: "english",
},
{
id: "tag4",
title: "Adults",
slug: "adults",
},
],
},
{
id: 4,
title: "How to study French",
tags: [
{
id: "tag3",
title: "French",
slug: "french",
},
{
id: "tag4",
title: "Adults",
slug: "adults",
},
],
},
]
const [filterTags, setFilterTags] = useState([])
const filteredDATA = DATA.filter(node =>
filterTags.length > 0
? filterTags.every(filterTag =>
node.tags.map(tag => tag.slug).includes(filterTag)
)
: DATA
)
const filterHandler = event => {
if (event.target.checked) {
setFilterTags([...filterTags, event.target.value])
} else {
setFilterTags(
filterTags.filter(filterTag => filterTag !== event.target.value)
)
}
}
return (
<>
<div>
<label htmlFor="english">
<input
type="checkbox"
onChange={filterHandler}
value="english"
id="english"
/>
<span>English</span>
</label>
<label htmlFor="french">
<input
type="checkbox"
onChange={filterHandler}
value="french"
id="french"
/>
<span>French</span>
</label>
<label htmlFor="kids">
<input
type="checkbox"
onChange={filterHandler}
value="kids"
id="kids"
/>
<span>Kids</span>
</label>
<label htmlFor="adults">
<input
type="checkbox"
onChange={filterHandler}
value="adults"
id="adults"
/>
<span>Adults</span>
</label>
</div>
<ul>
{filteredDATA.map(node => (
<li key={node.id}>{node.title}</li>
))}
</ul>
</>
)
}
今回はソート(並び替え)を入れていませんが、DATA
をソートして、そのソートしたデータを元にfilteredDATA
を生成すれば、ソート(並び替え)をしながら絞り込みをすることも可能です。いずれ解説したいと思います。