個人ブログサイトを作った際、記事一覧画面にキーワード検索やカテゴリーで絞り込む機能を実装しました。
APIから取得したコンテンツなどフロントエンドで保持しているデータに対して絞り込みを行う機能は色んな場面で使えるかなと思うので、そのロジックと具体的な実装方法をまとめておきます。
基本的なロジック
データの絞り込みを行う基本的なロジックは、JavaScriptの組み込み関数のfitler
を利用した配列操作です。
filter()
メソッドは、与えられた関数によって実装されたテストに合格したすべての配列からなる新しい配列を生成します。
["りんご", "みかん", "いちご"].filter((text) => {
return text !== "りんご";
});
// #=> ["みかん", "いちご"];
filterメソッドを使ってReactで検索機能を実装する流れは以下の通りです。
- ユーザーから検索条件を受け取る。(キーワードの入力、カテゴリーの選択など)
- 全てのデータが入った配列に対して、filterメソッドにより1.で受け取った検索条件に合致するデータのみを選別して新しい配列を生成する。
- 2.で生成された配列の中身を画面に一覧表示する。
検索機能の実装例
以下のURLはこれから説明する検索機能を実装したサンプルコードです。
サンプルコードにある、①カテゴリー選択ボタンによる検索
と②フリーキーワード検索
について解説していきます。
実際のWebサービスでは、ブログ記事のデータはAPIから受け取ることが多いかと思いますが、今回は予め posts
という配列で記事データを用意し、フロント側で保持しておきます。
【検索対象とする記事データ】
const posts = [
{
title: 'useStateの使い方',
category: 'React'
},
{
title: 'LaravelのMVCモデルについて',
category: 'Laravel'
},
{
title: '同一オリジンポリシーとCORS',
category: 'Web'
},
{
title: 'useEffectの使い方',
category: 'React'
}
]
そして、画面に表示する記事データを管理するための状態変数 showPosts
を用意し、showPosts
の記事を一覧表示します。
showPosts
の初期値には、上記のposts
を入れておきます。
export default function App() {
const [showPosts, setShowPosts] = useState(posts);
return (
<div className="App">
<h1>記事一覧</h1>
{showPosts.map((post, index) => {
return (
<div key={post.title}>
<p>{index+1}. {post.title}</p>
<p>category:{post.category}</p>
</div>
);
})}
</div>
);
}
ブラウザに表示すると以下の通りです。
この記事一覧に対して絞り込みを行っていきます。
①カテゴリー選択ボタンによる検索
まずは、カテゴリー選択ボタンでカテゴリーを絞り込む機能を実装してみます。
実装手順
- カテゴリーリストの配列を作成
- カテゴリー選択ボタンを設置
-
onClick
時に実行して絞り込み処理を行うselectCategory
メソッドを定義
実装したコードはこちらです。
export default function App() {
const [showPosts, setShowPosts] = useState(posts);
// カテゴリーリスト
const categories = Array.from(new Set(posts.map((post) => post.category)));
// カテゴリー絞り込み
const selectCategory = (category) => {
// allを選択した場合は早期return
if (category === "all") {
setShowPosts(posts);
return;
}
const selectedPosts = posts.filter((post) => post.category === category);
setShowPosts(selectedPosts);
};
return (
<div className="App">
<h1>記事一覧</h1>
{/* カテゴリー選択ボタン */}
<div>
<h4>Category:</h4>
<button onClick={() => selectCategory("all")}>All</button>
{categories.map((category) => (
<button onClick={() => selectCategory(category)}>{category}</button>
))}
</div>
{showPosts.map((post, index) => {
return (
<div key={post.title}>
<p>
{index + 1}. {post.title}
</p>
<p>category:{post.category}</p>
</div>
);
})}
</div>
);
}
ポイント解説
ひとつずつ解説します。
1.カテゴリーリストの配列を作成
posts
配列に記事が存在するカテゴリーの分だけカテゴリー選択ボタンを設置したいので、まずはカテゴリーのリストを作成します。
// カテゴリーリスト
const categories = Array.from(new Set(posts.map((post) => post.category)));
JavaScriptのSetオブジェクト
を使って、posts
に存在するカテゴリーを重複排除して格納したカテゴリーリストを作っています。
Setオブジェクトについては以下の記事を参考にしました。
SetオブジェクトはES2015から導入された機能で、重複した値が無いことを保証してくれます。
Set
オブジェクトは値のコレクションです。挿入順に要素を反復することができます。Set
に重複する値は格納出来ません。Set
内の値はコレクション内で一意となります。
2. カテゴリー選択ボタンを設置
各カテゴリーを選択するボタンと、全ての記事を表示するためのAllボタンを作ります。
1.で作成したカテゴリーリストの分だけ、カテゴリー選択ボタンを設置します。
カテゴリー選択ボタンとAllボタンのonClick
時にはselectCategory
メソッドを実行し、引数には該当のカテゴリーを渡しています。
<button onClick={() => selectCategory("all")}>All</button>
{categories.map((category) => (
<button onClick={() => selectCategory(category)}>{category}</button>
))}
3. onClick時に実行するselectCategoryメソッドを定義
posts
の中から、選択されたカテゴリーと一致するカテゴリーの記事のみをfilter
メソッドで選別しています。
絞り込み後の配列を、一覧表示に使う状態変数showPosts
にセットしています。
const selectCategory = (category) => {
// allの場合は早期return
if (category === "all") {
setShowPosts(posts);
return;
}
const selectedPosts = posts.filter((post) => post.category === category);
setShowPosts(selectedPosts);
}
②フリーキーワード検索
次に、ユーザーの入力値による絞り込み(フリーキーワード検索)の機能を実装してみます。
実装手順
- 検索フォームへの入力値を保持する状態変数
inputValue
を定義 - フリーキーワード検索フォームを設置
- 検索フォームの
onChange
時に実行するhandleInputChange
メソッドを定義 - 検索フォームへの入力値が変わる度に実行して絞り込みを行う
search
メソッドを定義
export default function App() {
const [showPosts, setShowPosts] = useState(posts)
const [inputValue, setInputValue] = useState()
// 検索欄への入力値をハンドリング
const handleInputChange = (e) => {
setInputValue(e.target.value)
search(e.target.value)
}
// 検索欄への入力値での絞り込み
const search = (value) => {
// 検索欄への入力が空の場合は早期return
if (value === "") {
setShowPosts(posts);
return;
}
const serchedPosts = posts.filter(
(post) =>
Object.values(post).filter(
(item) =>
item !== undefined &&
item !== null &&
item.toUpperCase().indexOf(value.toUpperCase()) !== -1
).length > 0
);
setShowPosts(serchedPosts);
}
return (
<div className="App">
<h1>記事一覧</h1>
{/* フリーキーワード検索フォーム */}
<div>
<h4>Search</h4>
<input type="text" value={inputValue} onChange={handleInputChange} />
</div>
{/* 記事一覧表示 */}
{showPosts.map((post, index) => {
return (
<div key={post.title}>
<p>
{index + 1}. {post.title}
</p>
<p>category:{post.category}</p>
</div>
);
})}
</div>
);
}
ポイント解説
大体はReactの基本の知識でカバーできるかと思います。
4.の絞り込みを行うロジックの部分は一見ややこしいですが、一つずつ読み解いて解説していきます。
記事の絞り込みを行っているのは以下の部分です。
const serchedPosts = posts.filter(
(post) =>
Object.values(post).filter(
(item) =>
item !== undefined &&
item !== null &&
item.toUpperCase().indexOf(value.toUpperCase()) !== -1
).length > 0
)
このコードでは、
- タイトルもしくはカテゴリーのいずれかにユーザーの入力値を含む(完全一致ではない)
- 大文字小文字の違いは許容
という要件で絞り込んでいます。
要点を解説していきます。
- Object.values()
Object.values(post)
上記の部分で、オブジェクトのプロパティの値を配列で取得するObject.values()
メソッドを使い、検索対象の値を格納した配列を作ります。
- undefinedとnullを除外
item !== undefined &&
item !== null &&
次に出てくるtoUpperCase()
はnull
やundefined
の値に対しては使えません。
なので仮にこの2行が無いと、Obect.values(post)
で生成された配列の中にundefined
もしくはnull
があった場合 Cannot read properties of null (reading 'toUpperCase')
というエラーが出ます。
- toUpperCase()
toUpperCase()
メソッドは、呼び出す文字列の値を(文字列でない場合、文字列に変換して)大文字に変換して返します。
記事のタイトルとカテゴリーと、ユーザーの入力値の双方を大文字に変換することで、大文字・小文字の違いは許容します。
- indexOf()
indexOf()
メソッドは、呼び出す[String](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String)
オブジェクト中で、fromIndex
から検索を始め、指定された値が最初に現れたインデックスを返します。値が見つからない場合は-1
を返します。
item.toUpperCase().indexOf(value.toUpperCase()) !== -1
値が見つからなければ -1
を返すという特性を使って、記事のタイトルやカテゴリーにユーザーの入力値を含んでいれば true
、含まなければ false
を返すようにしています。
以上のステップを経て、以下のコードで検索キーワードを含む値が1つでもある記事は .length > 0
の結果 trueが返り、serchedPosts
に格納されます。
Object.values(member).filter(
(item: string) =>
item !== undefined &&
item !== null &&
item.toUpperCase().indexOf(value.toUpperCase()) !== -1
).length > 0
あとはこのserchedPosts
を画面に表示するだけです。
さいごに
Setオブジェクト、fileterメソッド、indexOfメソッド、toUpperCaseメソッドなど、JavaScriptの持つメソッドについても勉強になりました。
組み込み関数を一通りチラッとでも知っていれば、何かを実装したい時にあれを使えばできそう…って考えられると思うので、元々用意されている関数なんかの知識は増やしていきたいなと思います。
参考記事