Reactでリストの絞り込みを実装する

以前にReactでリストのデータを表示する記事を投稿しましたが、この中にフィルターの処理を追加してみます。

サンプルコード

以前使用したデータに、絞り込み用の項目を追加して使用します。

  const units = ["illumination STARS", "L’Antica", "放課後クライマックスガールズ", "ALSTROEMERIA", "Straylight", "noctchill", "SHHis", "CoMETIK"];
  const bloodTypes = ["A", "B", "O", "AB"];
  const idols = [
    { "unit": "illumination STARS", "name": "櫻木真乃", "kana": "MANO SAKURAGI", "birthplace": "東京都", "bloodType": "A", "age": 16 },
    { "unit": "illumination STARS", "name": "風野灯織", "kana": "HIORI KAZANO", "birthplace": "東京都", "bloodType": "A", "age": 15 },
    { "unit": "illumination STARS", "name": "八宮めぐる", "kana": "MEGURU HACHIMIYA", "birthplace": "アメリカ", "bloodType": "O", "age": 16 },
    { "unit": "L’Antica", "name": "月岡恋鐘", "kana": "KOGANE TSUKIOKA", "birthplace": "長崎県", "bloodType": "B", "age": 19 },
    { "unit": "L’Antica", "name": "田中摩美々", "kana": "MAMIMI TANAKA", "birthplace": "神奈川県", "bloodType": "B", "age": 18 },
    { "unit": "L’Antica", "name": "白瀬咲耶", "kana": "SAKUYA SHIRASE", "birthplace": "高知県", "bloodType": "A", "age": 18 },
    { "unit": "L’Antica", "name": "三峰結華", "kana": "YUIKA MITSUMINE", "birthplace": "福島県", "bloodType": "O", "age": 19 },
    { "unit": "L’Antica", "name": "幽谷霧子", "kana": "KIRIKO YUKOKU", "birthplace": "青森県", "bloodType": "AB", "age": 17 },
    { "unit": "放課後クライマックスガールズ", "name": "小宮果穂", "kana": "KAHO KOMIYA", "birthplace": "東京都", "bloodType": "A", "age": 12 },
    { "unit": "放課後クライマックスガールズ", "name": "園田智代子", "kana": "CHIYOKO SONODA", "birthplace": "千葉県", "bloodType": "A", "age": 17 },
    { "unit": "放課後クライマックスガールズ", "name": "西城樹里", "kana": "JURI SAIJO", "birthplace": "神奈川県", "bloodType": "O", "age": 17 },
    { "unit": "放課後クライマックスガールズ", "name": "杜野凛世", "kana": "RINZE MORINO", "birthplace": "鳥取県", "bloodType": "B", "age": 16 },
    { "unit": "放課後クライマックスガールズ", "name": "有栖川夏葉", "kana": "NATSUHA ARISUGAWA", "birthplace": "愛知県", "bloodType": "B", "age": 20 },
    { "unit": "ALSTROEMERIA", "name": "大崎甘奈", "kana": "AMANA OSAKI", "birthplace": "富山県", "bloodType": "A", "age": 17 },
    { "unit": "ALSTROEMERIA", "name": "大崎甜花", "kana": "TENKA OSAKI", "birthplace": "富山県", "bloodType": "A", "age": 17 },
    { "unit": "ALSTROEMERIA", "name": "桑山千雪", "kana": "CHIYUKI KUWAYAMA", "birthplace": "山口県", "bloodType": "A", "age": 23 },
    { "unit": "Straylight", "name": "芹沢あさひ", "kana": "ASAHI SERIZAWA", "birthplace": "東京都", "bloodType": "AB", "age": 14 },
    { "unit": "Straylight", "name": "黛冬優子", "kana": "FUYUKO MAYUZUMI", "birthplace": "茨城県", "bloodType": "A", "age": 19 },
    { "unit": "Straylight", "name": "和泉愛依", "kana": "MEI IZUMI", "birthplace": "埼玉県", "bloodType": "O", "age": 18 },
    { "unit": "noctchill", "name": "浅倉透", "kana": "TORU ASAKURA", "birthplace": "東京都", "bloodType": "B", "age": 17 },
    { "unit": "noctchill", "name": "樋口円香", "kana": "MADOKA HIGUCHI", "birthplace": "東京都", "bloodType": "B", "age": 17 },
    { "unit": "noctchill", "name": "福丸小糸", "kana": "KOITO FUKUMARU", "birthplace": "東京都", "bloodType": "O", "age": 16 },
    { "unit": "noctchill", "name": "市川雛菜", "kana": "HINANA ICHIKAWA", "birthplace": "神奈川県", "bloodType": "A", "age": 15 },
    { "unit": "SHHis", "name": "七草にちか", "kana": "NICHIKA NANAKUSA", "birthplace": "埼玉県", "bloodType": "O", "age": 16 },
    { "unit": "SHHis", "name": "緋田美琴", "kana": "MIKOTO AKETA", "birthplace": "北海道", "bloodType": "B", "age": 24 },
    { "unit": "CoMETIK", "name": "斑鳩ルカ", "kana": "LUCA IKARUGA", "birthplace": "神奈川県", "bloodType": "A", "age": 20 },
    { "unit": "CoMETIK", "name": "鈴木羽那", "kana": "HANA SUZUKI", "birthplace": "岡山県", "bloodType": "O", "age": 18 },
    { "unit": "CoMETIK", "name": "郁田はるき", "kana": "HARUKI IKUTA", "birthplace": "長野県", "bloodType": "AB", "age": 17 }
  ];

作業前の各コンポーネントファイルの内容は以下のようになっています。

App.jsx

import Unit from './Unit.jsx'

function App() {
  const units = ["illumination STARS", "L’Antica", "放課後クライマックスガールズ", "ALSTROEMERIA", "Straylight", "noctchill", "SHHis", "CoMETIK"];
  const bloodTypes = ["A", "B", "O", "AB"];
  const idols = [
    { "unit": "illumination STARS", "name": "櫻木真乃", "kana": "MANO SAKURAGI", "birthplace": "東京都", "bloodType": "A", "age": 16 },
    { "unit": "illumination STARS", "name": "風野灯織", "kana": "HIORI KAZANO", "birthplace": "東京都", "bloodType": "A", "age": 15 },
    { "unit": "illumination STARS", "name": "八宮めぐる", "kana": "MEGURU HACHIMIYA", "birthplace": "アメリカ", "bloodType": "O", "age": 16 },
    ~ 略 ~
    { "unit": "CoMETIK", "name": "鈴木羽那", "kana": "HANA SUZUKI", "birthplace": "岡山県", "bloodType": "O", "age": 18 },
    { "unit": "CoMETIK", "name": "郁田はるき", "kana": "HARUKI IKUTA", "birthplace": "長野県", "bloodType": "AB", "age": 17 }
  ];

  return (
    <>
      {
        // ユニットでループを回す
        units.map((unit) =>
          <Unit key={unit} unit={unit} idols={idols} />
        )
      }
    </>
  )
}

export default App

Unit.jsx

import Idol from './Idol.jsx'

function Unit( {unit, idols} ) {
  return (
    <section key={unit}>
      <h2>{unit}</h2>
      <ul>
        {
          // 所属ユニットのアイドルのみ追加
          idols
            .filter((idol) => idol.unit === unit)
            .map((idol) => (
              <Idol key={idol.name} idol={idol} />
            ))
        }
      </ul>
    </section>
  )
}

export default Unit

Idol.jsx

function Idol( {idol} ) {
  return (
    <li>
      {idol.name}({idol.age})
    </li>
  )
}

export default Idol

まずはkanaの項目を使って、テキスト入力での絞り込みを行ってみます。
App.jsxにテキスト入力のinputを配置します。

import { useState } from 'react'
import Unit from './Unit.jsx'

function App() {
  const units = ["illumination STARS", "L’Antica", "放課後クライマックスガールズ", "ALSTROEMERIA", "Straylight", "noctchill", "SHHis", "CoMETIK"];
  const bloodTypes = ["A", "B", "O", "AB"];
  const idols = [
    { "unit": "illumination STARS", "name": "櫻木真乃", "kana": "MANO SAKURAGI", "birthplace": "東京都", "bloodType": "A", "age": 16 },
    { "unit": "illumination STARS", "name": "風野灯織", "kana": "HIORI KAZANO", "birthplace": "東京都", "bloodType": "A", "age": 15 },
    { "unit": "illumination STARS", "name": "八宮めぐる", "kana": "MEGURU HACHIMIYA", "birthplace": "アメリカ", "bloodType": "O", "age": 16 },
    ~ 略 ~
    { "unit": "CoMETIK", "name": "鈴木羽那", "kana": "HANA SUZUKI", "birthplace": "岡山県", "bloodType": "O", "age": 18 },
    { "unit": "CoMETIK", "name": "郁田はるき", "kana": "HARUKI IKUTA", "birthplace": "長野県", "bloodType": "AB", "age": 17 }
  ];

  const [filterKeyword, setFilterKeyword] = useState('');

  return (
    <>
      <div>
        <input
          type="text"
          value={filterKeyword}
          onChange={(e) => setFilterKeyword(e.target.value)}
          placeholder="半角英字で入力してください。"
        />
      </div>

      {
        // ユニットでループを回す
        units.map((unit) =>
          <Unit key={unit} unit={unit} idols={idols} filterKeyword={filterKeyword} />
        )
      }
    </>
  )
}

export default App

入力したデータの管理にuseStateを使用して、Unitのコンポーネントにpropsで入力値を渡しています。

次にUnit.jsxで受け取った入力値を使って絞り込みを行います。

import Idol from './Idol.jsx'

function Unit( {unit, idols, filterKeyword} ) {
  return (
    <section key={unit}>
      <h2>{unit}</h2>
      <ul>
        {
          // 所属ユニットのアイドルのみ追加
          idols
            .filter((idol) => idol.unit === unit)
            .filter((idol) => idol.kana.indexOf(filterKeyword.toUpperCase()) !== -1)
            .map((idol) => (
              <Idol key={idol.name} idol={idol} />
            ))
        }
      </ul>
    </section>
  )
}

export default Unit

これでテキストでの絞り込み処理が実装できました。
テキストで絞り込みのデモページ

次の絞り込みを実装する前に、絞り込み設定のエリアを別コンポーネントに分割しておきます。
SearchBar.jsxを作成して、絞り込み設定のエリアを移します。

function SearchBar( {filterKeyword, setFilterKeyword} ) {
  return (
    <div>
      <input
        type="text"
        value={filterKeyword}
        onChange={(e) => setFilterKeyword(e.target.value)}
        placeholder="半角英字で入力してください。"
      />
    </div>
  )
}

export default SearchBar

App.jsx内でSearchBarのコンポーネントを使う形に変更します。

import { useState } from 'react'
import SearchBar from './SearchBar.jsx'
import Unit from './Unit.jsx'

function App() {
  const units = ["illumination STARS", "L’Antica", "放課後クライマックスガールズ", "ALSTROEMERIA", "Straylight", "noctchill", "SHHis", "CoMETIK"];
  const bloodTypes = ["A", "B", "O", "AB"];
  const idols = [
    { "unit": "illumination STARS", "name": "櫻木真乃", "kana": "MANO SAKURAGI", "birthplace": "東京都", "bloodType": "A", "age": 16 },
    { "unit": "illumination STARS", "name": "風野灯織", "kana": "HIORI KAZANO", "birthplace": "東京都", "bloodType": "A", "age": 15 },
    { "unit": "illumination STARS", "name": "八宮めぐる", "kana": "MEGURU HACHIMIYA", "birthplace": "アメリカ", "bloodType": "O", "age": 16 },
    ~ 略 ~
    { "unit": "CoMETIK", "name": "鈴木羽那", "kana": "HANA SUZUKI", "birthplace": "岡山県", "bloodType": "O", "age": 18 },
    { "unit": "CoMETIK", "name": "郁田はるき", "kana": "HARUKI IKUTA", "birthplace": "長野県", "bloodType": "AB", "age": 17 }
  ];

  const [filterKeyword, setFilterKeyword] = useState('');

  return (
    <>
      <SearchBar filterKeyword={filterKeyword} setFilterKeyword={setFilterKeyword} />

      {
        // ユニットでループを回す
        units.map((unit) =>
          <Unit key={unit} unit={unit} idols={idols} filterKeyword={filterKeyword} />
        )
      }
    </>
  )
}

export default App

これで絞り込みエリアを別コンポーネントにできました。

次にunitの項目を使って、チェックボックスの絞り込みを行ってみます。
まずはSearchBar.jsxにチェックボックスの配置と処理を追加します。

function SearchBar({
  filterKeyword,
  setFilterKeyword,
  filterUnits,
  setFilterUnits
}) {
  const updateFilterUnits = (e) => {
    const updateUnits = filterUnits.map((unit) => {
      const u = { ...unit };
      if(u.name === e.target.value) {
        u.checked = !u.checked;
      }
      return u;
    });
    setFilterUnits(updateUnits);
  }

  return (
    <>
      <div>
        <input
          type="text"
          value={filterKeyword}
          onChange={(e) => setFilterKeyword(e.target.value)}
          placeholder="半角英字で入力してください。"
        />
      </div>

      <div>
        {
          filterUnits.map((unit) => (
            <label key={unit.name} htmlFor={unit.name}>
              <input
                type="checkbox"
                value={unit.name}
                id={unit.name}
                checked={unit.checked}
                onChange={updateFilterUnits}
              />
              {unit.name}
            </label>
          ))
        }
      </div>
    </>
  )
}

export default SearchBar

まずpropsでユニットに関する情報(filterUnits)を受け取った上で、29~44行目でチェックボックスの配置を行っています。
チェックボックスの変更があった際はupdateFilterUnitsという関数を実行するようにしていて、関数は7~16行目になります。
updateFilterUnitsの関数では、変更を行ったチェックボックスのデータの更新を行っています。

次にApp.jsxを変更します。

import { useState } from 'react'
import SearchBar from './SearchBar.jsx'
import Unit from './Unit.jsx'

function App() {
  const units = ["illumination STARS", "L’Antica", "放課後クライマックスガールズ", "ALSTROEMERIA", "Straylight", "noctchill", "SHHis", "CoMETIK"];
  const bloodTypes = ["A", "B", "O", "AB"];
  const idols = [
    { "unit": "illumination STARS", "name": "櫻木真乃", "kana": "MANO SAKURAGI", "birthplace": "東京都", "bloodType": "A", "age": 16 },
    { "unit": "illumination STARS", "name": "風野灯織", "kana": "HIORI KAZANO", "birthplace": "東京都", "bloodType": "A", "age": 15 },
    { "unit": "illumination STARS", "name": "八宮めぐる", "kana": "MEGURU HACHIMIYA", "birthplace": "アメリカ", "bloodType": "O", "age": 16 },
    ~ 略 ~
    { "unit": "CoMETIK", "name": "鈴木羽那", "kana": "HANA SUZUKI", "birthplace": "岡山県", "bloodType": "O", "age": 18 },
    { "unit": "CoMETIK", "name": "郁田はるき", "kana": "HARUKI IKUTA", "birthplace": "長野県", "bloodType": "AB", "age": 17 }
  ];

  const [filterKeyword, setFilterKeyword] = useState('');
  const [filterUnits, setFilterUnits] = useState(units.map((unit) => ({
    "name": unit,
    "checked": true
  })));

  return (
    <>
      <SearchBar
        filterKeyword={filterKeyword}
        setFilterKeyword={setFilterKeyword}
        filterUnits={filterUnits}
        setFilterUnits={setFilterUnits}
      />

      {
        // ユニットでループを回す
        units.map((unit) =>
          <Unit
            key={unit}
            unit={unit}
            idols={idols}
            filterKeyword={filterKeyword}
            filterUnits={filterUnits}
          />
        )
      }
    </>
  )
}

export default App

ポイントとしては18~21行目のuseStateの設定で、ユニット名のみではなくその項目がチェック状態かどうかを管理する項目が必要になるため、データの調整を行ったうえで初期値の設定を行い、SearchBarとUnitのコンポーネントに渡しています。

最後にUnitのコンポーネントに絞り込みの処理を追加します。

import Idol from './Idol.jsx'

function Unit({
  unit,
  idols,
  filterKeyword,
  filterUnits
}) {
  return (
    <section key={unit}>
      <h2>{unit}</h2>
      <ul>
        {
          // 所属ユニットのアイドルのみ追加
          idols
            .filter((idol) => idol.unit === unit)
            .filter((idol) => idol.kana.indexOf(filterKeyword.toUpperCase()) !== -1)
            .filter((idol) => {
              return filterUnits.some((unit) => {
                return unit.name === idol.unit && unit.checked;
              });
            })
            .map((idol) => (
              <Idol key={idol.name} idol={idol} />
            ))
        }
      </ul>
    </section>
  )
}

export default Unit

チェックボックスで絞り込みのデモページ

次にbloodTypeの項目を使って、ラジオボタンの絞り込みを実装してみます。
まずはSearchBar.jsxにラジオボタンの配置と処理を追加します。

function SearchBar({
  bloodTypes,
  filterKeyword,
  setFilterKeyword,
  filterUnits,
  setFilterUnits,
  filterBloodTypeChecked,
  setFilterBloodTypeChecked
}) {
  const updateFilterUnits = (e) => {
    const updateUnits = filterUnits.map((unit) => {
      const u = { ...unit };
      if(u.name === e.target.value) {
        u.checked = !u.checked;
      }
      return u;
    });
    setFilterUnits(updateUnits);
  }

  return (
    <>
      <div>
        <input
          type="text"
          value={filterKeyword}
          onChange={(e) => setFilterKeyword(e.target.value)}
          placeholder="半角英字で入力してください。"
        />
      </div>

      <div>
        {
          filterUnits.map((unit) => (
            <label key={unit.name} htmlFor={unit.name}>
              <input
                type="checkbox"
                value={unit.name}
                id={unit.name}
                checked={unit.checked}
                onChange={updateFilterUnits}
              />
              {unit.name}
            </label>
          ))
        }
      </div>

      <div>
        {
          bloodTypes.map((bloodType) => (
            <label key={bloodType} htmlFor={bloodType}>
              <input
                type="radio"
                name="bloodType"
                value={bloodType}
                id={bloodType}
                checked={bloodType === filterBloodTypeChecked}
                onChange={(e) => setFilterBloodTypeChecked(e.target.value)}
              />
              {bloodType}
            </label>
          ))
        }
      </div>
    </>
  )
}

export default SearchBar

propsで受け取る値はbloodTypesとfilterBloodTypeChecked、setFilterBloodTypeCheckedになります。
bloodTypesは血液型の入った配列で49~65行目のラジオボタンの配置に使用、filterBloodTypeCheckedには現在選択中の値が入るのでchecked属性の判定で使用、setFilterBloodTypeCheckedは更新用の関数なのでonChangeで変更があった際に更新するようにしています。

次にApp.jsxを変更します。

import { useState } from 'react'
import SearchBar from './SearchBar.jsx'
import Unit from './Unit.jsx'

function App() {
  const units = ["illumination STARS", "L’Antica", "放課後クライマックスガールズ", "ALSTROEMERIA", "Straylight", "noctchill", "SHHis", "CoMETIK"];
  const bloodTypes = ["A", "B", "O", "AB"];
  const idols = [
    { "unit": "illumination STARS", "name": "櫻木真乃", "kana": "MANO SAKURAGI", "birthplace": "東京都", "bloodType": "A", "age": 16 },
    { "unit": "illumination STARS", "name": "風野灯織", "kana": "HIORI KAZANO", "birthplace": "東京都", "bloodType": "A", "age": 15 },
    { "unit": "illumination STARS", "name": "八宮めぐる", "kana": "MEGURU HACHIMIYA", "birthplace": "アメリカ", "bloodType": "O", "age": 16 },
    ~ 略 ~
    { "unit": "CoMETIK", "name": "鈴木羽那", "kana": "HANA SUZUKI", "birthplace": "岡山県", "bloodType": "O", "age": 18 },
    { "unit": "CoMETIK", "name": "郁田はるき", "kana": "HARUKI IKUTA", "birthplace": "長野県", "bloodType": "AB", "age": 17 }
  ];

  const [filterKeyword, setFilterKeyword] = useState('');
  const [filterUnits, setFilterUnits] = useState(units.map((unit) => ({
    "name": unit,
    "checked": true
  })));
  const [filterBloodTypeChecked, setFilterBloodTypeChecked] = useState(bloodTypes[0]);

  return (
    <>
      <SearchBar
        bloodTypes={bloodTypes}
        filterKeyword={filterKeyword}
        setFilterKeyword={setFilterKeyword}
        filterUnits={filterUnits}
        setFilterUnits={setFilterUnits}
        filterBloodTypeChecked={filterBloodTypeChecked}
        setFilterBloodTypeChecked={setFilterBloodTypeChecked}
      />

      {
        // ユニットでループを回す
        units.map((unit) =>
          <Unit
            key={unit}
            unit={unit}
            idols={idols}
            filterKeyword={filterKeyword}
            filterUnits={filterUnits}
            filterBloodTypeChecked={filterBloodTypeChecked}
          />
        )
      }
    </>
  )
}

export default App

血液型の選択の初期値はbloodTypesの先頭(A型)にしています。

次にUnitのコンポーネントで血液型での絞り込みを追加します。

import Idol from './Idol.jsx'

function Unit({
  unit,
  idols,
  filterKeyword,
  filterUnits,
  filterBloodTypeChecked
}) {
  return (
    <section key={unit}>
      <h2>{unit}</h2>
      <ul>
        {
          // 所属ユニットのアイドルのみ追加
          idols
            .filter((idol) => idol.unit === unit)
            .filter((idol) => idol.kana.indexOf(filterKeyword.toUpperCase()) !== -1)
            .filter((idol) => {
              return filterUnits.some((unit) => {
                return unit.name === idol.unit && unit.checked;
              });
            })
            .filter((idol) => idol.bloodType === filterBloodTypeChecked)
            .map((idol) => (
              <Idol key={idol.name} idol={idol} />
            ))
        }
      </ul>
    </section>
  )
}

export default Unit

最後にIdolのコンポーネントで、確認用に血液型を表示します。

function Idol( {idol} ) {
  return (
    <li>
      {idol.name}({idol.age})
      血液型:{idol.bloodType}
    </li>
  )
}

export default Idol

これでラジオボタンでの絞り込みが追加できました。
ラジオボタンで絞り込みのデモページ

最後にbirthplaceの項目を使って、セレクトボックスの絞り込みを実装してみます。
まずはSearchBar.jsxです。

function SearchBar({
  bloodTypes,
  birthplaces,
  filterKeyword,
  setFilterKeyword,
  filterUnits,
  setFilterUnits,
  filterBloodTypeChecked,
  setFilterBloodTypeChecked,
  filterBirthplaceChecked,
  setFilterBirthplaceChecked
}) {
  const updateFilterUnits = (e) => {
    const updateUnits = filterUnits.map((unit) => {
      const u = { ...unit };
      if(u.name === e.target.value) {
        u.checked = !u.checked;
      }
      return u;
    });
    setFilterUnits(updateUnits);
  }

  return (
    <>
      <div>
        <input
          type="text"
          value={filterKeyword}
          onChange={(e) => setFilterKeyword(e.target.value)}
          placeholder="半角英字で入力してください。"
        />
      </div>

      <div>
        {
          filterUnits.map((unit) => (
            <label key={unit.name} htmlFor={unit.name}>
              <input
                type="checkbox"
                value={unit.name}
                id={unit.name}
                checked={unit.checked}
                onChange={updateFilterUnits}
              />
              {unit.name}
            </label>
          ))
        }
      </div>

      <div>
        {
          bloodTypes.map((bloodType) => (
            <label key={bloodType} htmlFor={bloodType}>
              <input
                type="radio"
                name="bloodType"
                value={bloodType}
                id={bloodType}
                checked={bloodType === filterBloodTypeChecked}
                onChange={(e) => setFilterBloodTypeChecked(e.target.value)}
              />
              {bloodType}
            </label>
          ))
        }
      </div>

      <div>
        <select
          value={filterBirthplaceChecked}
          onChange={(e) => setFilterBirthplaceChecked(e.target.value)}
        >
          {
            birthplaces.map((birthplace) => (
              <option key={birthplace} value={birthplace}>
                {birthplace}
              </option>
            ))
          }
        </select>
      </div>
    </>
  )
}

export default SearchBar

エリアの情報をbirthplacesで受け取り、セレクトボックスのoptionを出力しています。
Reactの場合、セレクトボックスの選択状態はselectにvalueを設定して行うので、propsから受け取ったfilterBirthplaceCheckedを設定しています。

次にApp.jsxです。

import { useState } from 'react'
import SearchBar from './SearchBar.jsx'
import Unit from './Unit.jsx'

function App() {
  const units = ["illumination STARS", "L’Antica", "放課後クライマックスガールズ", "ALSTROEMERIA", "Straylight", "noctchill", "SHHis", "CoMETIK"];
  const bloodTypes = ["A", "B", "O", "AB"];
  const idols = [
    { "unit": "illumination STARS", "name": "櫻木真乃", "kana": "MANO SAKURAGI", "birthplace": "東京都", "bloodType": "A", "age": 16 },
    { "unit": "illumination STARS", "name": "風野灯織", "kana": "HIORI KAZANO", "birthplace": "東京都", "bloodType": "A", "age": 15 },
    { "unit": "illumination STARS", "name": "八宮めぐる", "kana": "MEGURU HACHIMIYA", "birthplace": "アメリカ", "bloodType": "O", "age": 16 },
    ~ 略 ~
    { "unit": "CoMETIK", "name": "鈴木羽那", "kana": "HANA SUZUKI", "birthplace": "岡山県", "bloodType": "O", "age": 18 },
    { "unit": "CoMETIK", "name": "郁田はるき", "kana": "HARUKI IKUTA", "birthplace": "長野県", "bloodType": "AB", "age": 17 }
  ];

  const [filterKeyword, setFilterKeyword] = useState('');
  const [filterUnits, setFilterUnits] = useState(units.map((unit) => ({
    "name": unit,
    "checked": true
  })));
  const [filterBloodTypeChecked, setFilterBloodTypeChecked] = useState(bloodTypes[0]);
  const birthplaces = Array.from(new Set(idols.map((idol) => idol.birthplace)));
  const [filterBirthplaceChecked, setFilterBirthplaceChecked] = useState(birthplaces[0]);

  return (
    <>
      <SearchBar
        bloodTypes={bloodTypes}
        birthplaces={birthplaces}
        filterKeyword={filterKeyword}
        setFilterKeyword={setFilterKeyword}
        filterUnits={filterUnits}
        setFilterUnits={setFilterUnits}
        filterBloodTypeChecked={filterBloodTypeChecked}
        setFilterBloodTypeChecked={setFilterBloodTypeChecked}
        filterBirthplaceChecked={filterBirthplaceChecked}
        setFilterBirthplaceChecked={setFilterBirthplaceChecked}
      />

      {
        // ユニットでループを回す
        units.map((unit) =>
          <Unit
            key={unit}
            unit={unit}
            idols={idols}
            filterKeyword={filterKeyword}
            filterUnits={filterUnits}
            filterBloodTypeChecked={filterBloodTypeChecked}
            filterBirthplaceChecked={filterBirthplaceChecked}
          />
        )
      }
    </>
  )
}

export default App

birthplacesのデータは用意していないので、23行目でidolsのデータからbirthplaceの値のみの配列を作成して、重複した値を除去して使用しています。

次にUnitのコンポーネントです。

import Idol from './Idol.jsx'

function Unit({
  unit,
  idols,
  filterKeyword,
  filterUnits,
  filterBloodTypeChecked,
  filterBirthplaceChecked
}) {
  return (
    <section key={unit}>
      <h2>{unit}</h2>
      <ul>
        {
          // 所属ユニットのアイドルのみ追加
          idols
            .filter((idol) => idol.unit === unit)
            .filter((idol) => idol.kana.indexOf(filterKeyword.toUpperCase()) !== -1)
            .filter((idol) => {
              return filterUnits.some((unit) => {
                return unit.name === idol.unit && unit.checked;
              });
            })
            .filter((idol) => idol.bloodType === filterBloodTypeChecked)
            .filter((idol) => idol.birthplace === filterBirthplaceChecked)
            .map((idol) => (
              <Idol key={idol.name} idol={idol} />
            ))
        }
      </ul>
    </section>
  )
}

export default Unit

最後にIdolのコンポーネントに出身地を表示します。

function Idol( {idol} ) {
  return (
    <li>
      {idol.name}({idol.age})
      血液型:{idol.bloodType}
      出身地:{idol.birthplace}
    </li>
  )
}

export default Idol

セレクトボックスで絞り込みのデモページ

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

関連記事

コメントを残す

メールアドレスが公開されることはありません。
* が付いている欄は必須項目です

CAPTCHA


コメントが承認されるまで時間がかかります。

2024年5月
 1234
567891011
12131415161718
19202122232425
262728293031