LoginSignup
19
12

More than 1 year has passed since last update.

Reactでリアルタイムの検索機能を実装する

Last updated at Posted at 2022-02-06

個人ブログサイトを作った際、記事一覧画面にキーワード検索やカテゴリーで絞り込む機能を実装しました。

APIから取得したコンテンツなどフロントエンドで保持しているデータに対して絞り込みを行う機能は色んな場面で使えるかなと思うので、そのロジックと具体的な実装方法をまとめておきます。

基本的なロジック

データの絞り込みを行う基本的なロジックは、JavaScriptの組み込み関数のfitlerを利用した配列操作です。

filter() メソッドは、与えられた関数によって実装されたテストに合格したすべての配列からなる新しい配列を生成します。

["りんご", "みかん", "いちご"].filter((text) => {
  return text !== "りんご";
});

// #=> ["みかん", "いちご"];

filterメソッドを使ってReactで検索機能を実装する流れは以下の通りです。

  1. ユーザーから検索条件を受け取る。(キーワードの入力、カテゴリーの選択など)
  2. 全てのデータが入った配列に対して、filterメソッドにより1.で受け取った検索条件に合致するデータのみを選別して新しい配列を生成する。
  3. 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>
  );
}

ブラウザに表示すると以下の通りです。
スクリーンショット 2022-01-11 20.59.12.png
この記事一覧に対して絞り込みを行っていきます。

①カテゴリー選択ボタンによる検索

まずは、カテゴリー選択ボタンでカテゴリーを絞り込む機能を実装してみます。

実装手順

  1. カテゴリーリストの配列を作成
  2. カテゴリー選択ボタンを設置
  3. 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);
}

②フリーキーワード検索

次に、ユーザーの入力値による絞り込み(フリーキーワード検索)の機能を実装してみます。

実装手順

  1. 検索フォームへの入力値を保持する状態変数inputValueを定義
  2. フリーキーワード検索フォームを設置
  3. 検索フォームのonChange時に実行するhandleInputChangeメソッドを定義
  4. 検索フォームへの入力値が変わる度に実行して絞り込みを行う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()nullundefinedの値に対しては使えません。

なので仮にこの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の持つメソッドについても勉強になりました。

組み込み関数を一通りチラッとでも知っていれば、何かを実装したい時にあれを使えばできそう…って考えられると思うので、元々用意されている関数なんかの知識は増やしていきたいなと思います。

参考記事

19
12
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
19
12