Tips

【Reactでも使える】JavaScriptでfilterを使って絞り込みをしてみる

  • このエントリーをはてなブックマークに追加

JavaScriptのfilter関数はReactで初めて知った人も多いのではないでしょうか?

JavaScriptを詳しくない状態でいきなり登場されると「何やってるんだ?」となってしまっています。

「絞り込み」「フィルタリング」のような機能を作りたい時に活躍してくれるので共有です。

また動画もあるので必要に応じて使ってください。

https://youtu.be/7OqMcUlzVLo

JavaScriptにおけるfilterの使い方

まずは簡単な例で見ていきましょう。

let array = [1,2,3,4,5];

array = array.filter((num) => num > 2);

console.log(array);

上記のコードでは配列arrayのなかで「2より大きい数字」という条件でコンソールに出力しています。

また注目すべきは「配列」になっていることです。

実は上記のコードでは少し簡略化していて、同じことを以下のようにも書くことができます。

let array = [1,2,3,4,5];

array = array.filter((num) => {
   return num > 2;
});

console.log(array);

書き方としてmapのような繰り返し処理に近いものがありますね。

実はfilter関数も繰り返し処理のように「配列の中を順番に見る」ことをやっています。

引数numとしている部分では、配列の中から取り出された1個の値が代入されます。

ちなみにnumは自分の好きな名前を付けることができますが、「配列の中から取り出されたもの」が分かるような名前にすることをおススメします。

JavaScriptのfilterを使って絞り込み機能を作る

よくある例として絞り込み機能を作ってみましょう。

まずHTMLは簡単に以下のように作っていきます。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./style.css">
  <script src="./script.js" defer></script>
</head>
<body>
  <ul class="news">
    <li>aaa</li>
    <li>bbb</li>
    <li>ccc</li>
    <li>ddd</li>
    <li>eee</li>
  </ul>
  <div class="container">
    <button class="btn" data-year="2019">2019年</button>
    <button class="btn" data-year="2020">2020年</button>
    <button class="btn" data-year="2021">2021年</button>
  </div>
  <ul class="list"></ul>
</body>
</html>

ホームページのお知らせ一覧を想定してみました。

各タイトルの下に年次のボタンがあり、ボタンをクリックすると対象のお知らせだけを出力することを目指します。

本来であればデータベースからデータを引っ張ってくるところですが、今回は簡易的にJavaScriptでデータを持つようにします。

const newsList = [
  {title: "ニュース1です", year: 2019},
  {title: "ニュース2です", year: 2020},
  {title: "ニュース3です", year: 2019},
  {title: "ニュース4です", year: 2021},
  {title: "ニュース5です", year: 2019},
];

配列newsListにお知らせがオブジェクトで格納されています。

お知らせのプロパティは「title(タイトル)」「year(年次)」の2つになっています。

const newsList = [
  {title: "ニュース1です", year: 2019},
  {title: "ニュース2です", year: 2020},
  {title: "ニュース3です", year: 2019},
  {title: "ニュース4です", year: 2021},
  {title: "ニュース5です", year: 2019},
];

document.querySelectorAll(".btn").forEach((data) => {
  data.addEventListener("click", (e) => {
    onClick(e);
  });
});

function onClick(e){
  //クリックされたボタンはHTML側で「data-year」で取得
  const button = e.target;
  const year = button.dataset.year;
 //newsList内の「year」とHTML側の「data-year」を突合せする
  const filteredList = newsList.filter((news) => news.year == year);
 //filterで突き合わせた結果をrenderListに渡す
  renderList(filteredList);
}

function renderList(filteredList){
  let list = "";
  for(const filtered of filteredList){
    list += `<li>${filtered.title}</li>`;
  }
  document.querySelector(".list").innerHTML = list;
}

JavaScriptのコードの全体像としては上記のようになります。

関数onCickの中でfilterを使って、newsLIstのなかのyear(年次)とクリックされたボタンの年次とを突き合わせています。

filterは実行結果を「配列」で返すことができると冒頭でお話したように、今回も定数filteredListに配列で入れています。

定数filteredListの中には「クリックされたボタンと同じ年次のデータ」が配列で格納されているので、renderListのような表示結果を作る関数にfilteredListを引数で渡してあげることができます。

ちなみにクリックしたボタンの年次についてはHTML側で「data-year」属性で予め年次の数字を指定していました。

<button class="btn" data-year="2019">2019年</button>
<button class="btn" data-year="2020">2020年</button>
<button class="btn" data-year="2021">2021年</button>
~省略~

function onClick(e){
  //クリックされたボタンはHTML側で「data-year」で取得
  const button = e.target;
  const year = button.dataset.year;
 ~省略~
}

~省略~

実際の表示結果を確認してみましょう。

まずは2019年をクリックします。

続いて2020年をクリックします。

2021年も同じように動作します。

クリックされたボタンによって対象となるデータを出力することが出来ました。

今回のような例はWebサイトやWebアプリではよく登場するので、JavaScriptだけでなくReactのようなモダンJSでも使うことになると思います。

まずはJavaScriptでの挙動を知っておくことで、Reactなどでもスムーズに使うことが出来るようになるでしょう。

Reactにおけるfilterの使い方

最後にReactプロジェクトでfilterを使う例も載せておきます。

例として以下のようにTODOリストを作成して、検索したら特定のタスクだけ表示させるものを作ってみます。

コードとしては以下のようにしました。

TODOリストをitemsというステートに、検索結果をfilteredというステートにしてuseStateで管理しています。

import React, { useRef, useState } from 'react';

const App = () => {
  // 入力結果と検索結果を別のステートとして分けて作る例
  const [items, setItems] = useState([]);
  const [filtered, setFiltered] = useState([]);
  const inputRef = useRef();
  const onSubmit = (e) => {
    e.preventDefault();
    const inputValue = inputRef.current.value;
    if (inputValue === '') return;
    setItems((prev) => {
      return [...prev, inputValue];
    });
    setFiltered((prev) => {
      return [...prev, inputValue];
    });
    inputRef.current.value = '';
  };

  const onChange = (e) => {
    const searchValue = e.target.value;
    setFiltered(items.filter((p) => p.includes(searchValue)));
  };
  return (
    <div>
      検索:
      <input onChange={onChange} type="search" />
      <form onSubmit={onSubmit}>
        新規追加:
        <input type="text" ref={inputRef} />
        <button>送信</button>
      </form>
      {filtered.map((filterItem, i) => (
        <p>{filterItem}</p>
      ))}
    </div>
  );
};

export default App;

一覧表示部分がfilteredという検索結果の方で表示している理由としては、検索欄が使用されなかった場合に「””で検索した結果」になるためです。

「””で検索した結果」というのは全てのタスクが対象になり、結果的にTODOリストの全件表示することと同じになります。

これをTODOリストのitemsの方でやると、キーワードでフィルタリングした結果である検索結果を表示できないことになります。

このようにTODOリストと検索結果とで2つのuseStateを管理するのがわかりづらい場合には以下のようにすることもできます。

別解として参考にしてください。

import React, { useRef, useState } from 'react';

const App = () => {
  const [items, setItems] = useState([]);
  const [query, setQuery] = useState('');
  const inputRef = useRef();
  const onSubmit = (e) => {
    e.preventDefault();
    const inputValue = inputRef.current.value;
    if (inputValue === '') return;
    setItems((prev) => {
      return [...prev, inputValue];
    });
    inputRef.current.value = '';
  };

  const onChange = (e) => {
    setQuery(e.target.value);
  };
  // ステートではなくクエリに基づいて作成される配列にする
  const filtered = items.filter((item) => item.includes(query));

  return (
    <div>
      検索:
      <input value={query} onChange={onChange} type="search" />
      <form onSubmit={onSubmit}>
        新規追加:
        <input type="text" ref={inputRef} />
        <button>送信</button>
      </form>
      {filtered.map((filteredItem, i) => (
        <p>{filteredItem}</p>
      ))}
    </div>
  );
};

export default App;

こちらの例ではTODOリストを引き続きitemsというステートにしていますが、検索結果はステートでは管理していません。

一方で検索キーワードの方をqueryとしてuseRefで取得しました。

検索キーワードは一覧にして画面に表示するわけでは無いので、これでTODOリストのみをuseStateで管理することでシンプルになったはずです。

そもそも検索結果についてはステートで管理しなくてもfilter関数の戻り値で取得できるからです。

const filtered = items.filter((item) => item.includes(query));とすれば良いだけでも取得できるので、

const [filtered, setFiltered] = useState([ ])と書かなくて良いわけですね。

どちらの例も実際に手元で試して同じ動きになるのか確認してみてください。

また今回参考にした本は以下になりますのでよければどうぞ。

  • このエントリーをはてなブックマークに追加