36
33

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 3 years have passed since last update.

React #2Advent Calendar 2020

Day 21

React Hooks でキーワードによる絞り込み検索を実装する

Posted at

やりたいこと

APIから取得しフロントエンド側でプールしているデータを、テキスト入力などのUIからキーワードでリアルタイムに絞り込みを行う、インクリメンタルサーチのようなものを実現したい機会があったので作ってみた。

つくったもの

サンプルコード:https://codesandbox.io/s/filtering-search-demo-btjgn
React v16.12.0 を使用しています。
UIフレームワークは前回と同じく Material UI でサクっと用意。

ソースコードとコンポーネントについて

検索対象となるデータ

クライアント側で検索したいデータはサーバーサイドから fetch してきたレスポンスに基づいたものや、定数的にもともと保持しているものを使いたいケースが考えられますが、今回はサクっと実例だけを提示したかったため tsx のファイルにとりあえずサンプルとなるものをstring配列として定義しました。

Search.tsx
const products: string[] = [
  "Apple",
  "Banana",
  "Orange",
  "Cheese Cake",
  "Banana Cake",
  "Apple Juice",
  "Orange Juice"
];

<SearchTextField />

検索キーワードの入力を受け付けるUIです。
また、onClick時に対象となるデータを List 形式にしてぶら下げて表示させています。

Search.tsx
const SearchTextField: React.FC = () => {
  // 入力キーワード
  const [keyword, setKeyword] = useState("");
  // itemsのListを表示・非表示を切替。onClick で true を渡して表示させる
  const [showLists, setShowLists] = useState(false);
  // List 形式で表示するデータ。初期値では検索キーワードを入力していないので上で定義した
  // products を全件渡している
  const [filteredProducts, setFilteredProducts] = useState(products);

絞り込み検索のロジック

今回はuseEffectを用いて、keywordが入力される毎にproductsの中からそれに合致する要素を抜き出して新たに配列を生成し、filteredItemsに格納しています。
useEffect内で行なっている処理について一つずつ注釈していきます。

Search.tsx
useEffect(() => {
  if (keyword === "") {
    setFilteredProducts(products);
    return;
  }

ここでは一度入力したキーワードを全て消去した時(テキストフィールドの値が空文字""になった時)、絞り込み内容をリセットして初期値の products を改めて全件セットしています。同時に List コンポーネントを非表示にしたいパターンもあると思うのでその辺の実装はお好みで。

Search.tsx
const searchKeywords = keyword
  .trim()
  .toLowerCase()
  .match(/[^\s]+/g);

ここでは、入力されたキーワードを検索条件として使用するために配列に変更して格納しています。キーワードの間に空白がある場合、その空白を区切りにした配列をmatch()メソッドを用いて生成しています。この配列は、後述のevery()メソッドで検索条件として使用します。

入力されたキーワードの両端に空白があるとノイズになり検索の妨げとなるので、trim()でそれらを削ったあと、toLowerCase()ですべて小文字に変換しています。後述の処理(Array.every)で英字に関しては大文字・小文字の一致もチェックする仕様となっているので、それらの区別なくキーワードで絞り込みを行いたい時はtoLowerCaseでの変換が必須となります。
MDN - Array.prototype.every()

また、matchの中に記述している/[^\s]+/gという正規表現についてですが、[^\s]+は空白文字(半角・全角)以外の任意の一文字以上の連続にヒットさせる条件です。
gフラグはmatchメソッドのオプションで、文字列の中から正規表現に一致する要素を全て配列として返却させることができます。

Search.tsx
const result = products.filter((product) =>
  searchKeywords.every((kw) => product.toLowerCase().indexOf(kw) !== -1)
);

setFilteredProducts(result.length ? result : ["No Item Found"]);

最後に、入力したキーワード群を文字列として含む結果を絞り込む処理について解説します。
filter()メソッドは配列の各要素を任意の関数(boolean を返り値とするもの)にわたし、trueとなるものを絞り込み、新たに配列を生成します。
以下はfilterメソッドが各要素を渡す関数です。

searchKeywords.every((kw) => product.toLowerCase().indexOf(kw) !== -1)

searchKeywordsには検索条件として入力されたキーワードが格納されています。それらをevery()メソッドで一つずつ取り出し、 product 内に文字として含まれているか突き合わせ、indexOf()で精査しています。
検索キーワードがproductの文字列に含まれていると値として-1が返ってこないため、上記の関数の返り値は"true" となり、対象となるproductresult内の配列に格納されます。indexOf()で精査することによってキーワードとデータの文字列が部分一致している場合でも絞り込みと抽出が可能となっています。

大文字・小文字の区別なくヒットさせるため、searchKeywordsproductをともにtoLowerCase()で小文字に一律置き換えているのは前述のとおりです。

最後に

冒頭でもURLを記載しましたが、下記のサンプルコードから色々データを追加して動作確認などお試しください。
https://codesandbox.io/s/filtering-search-demo-btjgn

36
33
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
36
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?