カテゴリとステータスで絞り込もう

← 第12
Reactアプリ作成(JS編)13 / 15
14回 →

今回やること

前回までで、 気になるものを追加・更新・削除できるようになりました。

今回は一覧画面にカテゴリとステータスの選択欄を追加し、 選んだ条件に合うデータだけを表示できるようにします。

カテゴリやステータスを選ぶ
↓
選択した値をStateで管理する
↓
itemsから条件に合うデータだけを取り出す
↓
表示用のvisibleItemsを作る
↓
visibleItemsを一覧へ表示する

今回は、一覧データそのものを変更するわけではありません。

もとのitemsはそのまま残し、条件に合うデータだけを一覧に表示します。

スポンサーリンク

カテゴリとステータスの選択肢を読み込む

今回は、 第7回でoptions.jsへまとめた カテゴリとステータスの選択肢を一覧画面でも使います。

まずは、 src/components/ListScreen.jsxを開きます。

ファイルの先頭で、 useStateと選択肢を読み込んでください。

import { useState } from "react";
import { categories, statuses } from "../options";

categoriesstatusesは、 新規登録画面や編集画面でも使っている同じ配列です。

選択した値をStateで管理する

次に、 現在選択しているカテゴリとステータスを覚えておくStateを用意します。

ListScreenコンポーネントの先頭に、 次の2つのStateを追加してください。

const [selectedCategory, setSelectedCategory] = useState("すべて");
const [selectedStatus, setSelectedStatus] = useState("すべて");

selectedCategoryには現在選んでいるカテゴリ、 selectedStatusには現在選んでいるステータスが入ります。

最初はどちらも絞り込まないため、 初期値を"すべて"にしています。

カテゴリとステータスの選択欄を追加する

続いて、一覧画面へ2つのselectを追加します。

src/components/ListScreen.jsxh2の下に、次のコードを追加してください。

<div className="filters">
  <label>
    カテゴリ
    <select
      value={selectedCategory}
      onChange={(event) => setSelectedCategory(event.target.value)}
    >
      <option value="すべて">すべて</option>
      {categories.map((value) => (
        <option key={value} value={value}>
          {value}
        </option>
      ))}
    </select>
  </label>

  <label>
    ステータス
    <select
      value={selectedStatus}
      onChange={(event) => setSelectedStatus(event.target.value)}
    >
      <option value="すべて">すべて</option>
      {statuses.map((value) => (
        <option key={value} value={value}>
          {value}
        </option>
      ))}
    </select>
  </label>
</div>

どちらのselectにも、 絞り込みをしないための「すべて」を最初に追加しています。

その下には、 categoriesstatusesmap()optionとして表示しています。

選択内容が変わると、 setSelectedCategoryまたは setSelectedStatusによってStateが更新されます。

ファイルを保存して、ブラウザを確認してみましょう。

一覧画面に「カテゴリ」と「ステータス」の選択欄が表示されればOKです。

この時点では、選択肢を変更しても一覧の表示はまだ変わりません。 次に、選んだ値を使って一覧を絞り込めるようにします。

一覧画面にカテゴリとステータスの選択欄を追加した画面
一覧画面にカテゴリとステータスの選択欄が追加されています。

選んだ条件で一覧を絞り込む

選択欄を作れたので、 次は選んだ条件に合うデータだけを表示します。

前回はfilter()を使って、 削除するデータを除いた新しい配列を作りました。

今回も同じfilter()を使いますが、 条件に合うデータだけを表示するために使います。

ListScreenコンポーネントの2つのStateの下に、次のvisibleItemsを追加してください。

  const [selectedCategory, setSelectedCategory] = useState("すべて");
  const [selectedStatus, setSelectedStatus] = useState("すべて");

  const visibleItems = items
    .filter(
      (item) =>
        selectedCategory === "すべて" ||
        item.category === selectedCategory
    )
    .filter(
      (item) =>
        selectedStatus === "すべて" ||
        item.status === selectedStatus
    );

カテゴリが「すべて」の場合は、 すべてのデータを残します。

カテゴリを選んだ場合は、 item.categoryselectedCategoryと一致するデータだけを残します。

ステータスも同じ考え方です。

カテゴリ:本
ステータス:気になる

↓
categoryが「本」
かつ
statusが「気になる」

↓
両方の条件に合うデータだけ残る

2つのfilter()を順番に使うことで、 カテゴリとステータスの両方を組み合わせて絞り込めます。

visibleItemsを一覧に表示する

条件に合うデータを visibleItemsとして作れるようになりました。

ListScreenコンポーネントのtbodyの中にある、次の部分を探してください。

{items.map((item) => (

このitemsvisibleItemsへ変更します。

{visibleItems.map((item) => (

これで、 一覧に表示されるのは visibleItemsに残っているデータだけになります。

ファイルを保存して、 カテゴリやステータスを選んでみましょう。

選んだ条件に合わせて一覧が変わればOKです。

また、「すべて」に戻すと、 その条件での絞り込みが解除されることも確認してください。

visibleItemsはStateにしない

今回は、 selectedCategoryselectedStatusはStateとして管理しています。

一方、 visibleItemsはStateにしていません。

const visibleItems = items
  .filter(...)
  .filter(...);

visibleItemsは、 itemsと2つの選択値が分かれば その都度計算できる値だからです。

選択値のStateが更新されると ListScreenコンポーネントが再レンダリングされ、 新しい条件でvisibleItemsも計算し直されます。

すでにあるStateやpropsから計算できる値は、必要がなければ別のStateとして持たず、その都度計算できます。

条件に合うデータがない場合も表示する

カテゴリとステータスの組み合わせによっては、 条件に合うデータが1件もない場合があります。

テーブルの下に、次のコードを追加してください。

   </tbody>
  </table>
  {visibleItems.length === 0 && (<p>条件に合うデータがありません。</p>)}

visibleItems.length0の場合だけ、メッセージを表示します。

ブラウザで確認する

最後に、いくつか条件を変えて動きを確認してみましょう。

  • カテゴリだけを選ぶと、そのカテゴリのデータだけが表示される
  • ステータスだけを選ぶと、そのステータスのデータだけが表示される
  • 両方を選ぶと、2つの条件に合うデータだけが表示される
  • 「すべて」に戻すと、その条件での絞り込みが解除される
  • 条件に合うデータがない場合はメッセージが表示される

すべて確認できれば、 カテゴリとステータスによる絞り込みは完成です。

ReactとJavaScriptを分けて見てみよう

今回登場した内容を、 ReactとJavaScriptに分けて整理します。

今回登場したReactとJavaScriptの要素
種類 今回登場したもの
React State、更新関数、Stateの更新による再レンダリング
JavaScript filter()map()、条件式

選択したカテゴリやステータスを Stateとして管理する部分はReactです。

一方、 filter()を使って 条件に合うデータだけを取り出す部分はJavaScriptです。

Stateが変わると再レンダリングされ、 新しい条件でvisibleItemsが計算されることで 一覧の表示も変わります。

今回のまとめ

  • 一覧画面にカテゴリとステータスの選択欄を追加した
  • options.jsの選択肢を一覧画面でも再利用した
  • 選択した値をselectedCategoryselectedStatus Stateで管理した
  • filter()で条件に合うデータだけを取り出した
  • 2つのfilter()を使ってカテゴリとステータスの条件を組み合わせた
  • 一覧表示に使う配列をitemsからvisibleItemsへ変更した
  • visibleItemsはStateにせず、既存の値から計算した
  • 条件に合うデータがない場合にメッセージを表示した

今回の完成コード

今回変更した ListScreen.jsxの完成コードです。

src/components/ListScreen.jsx

import { useState } from "react";

import { categories, statuses } from "../options";

export default function ListScreen({ items, onCreate, onEdit }) {
  const [selectedCategory, setSelectedCategory] = useState("すべて");
  const [selectedStatus, setSelectedStatus] = useState("すべて");

  const visibleItems = items
    .filter(
      (item) =>
        selectedCategory === "すべて" ||
        item.category === selectedCategory
    )
    .filter(
      (item) =>
        selectedStatus === "すべて" ||
        item.status === selectedStatus
    );

  return (
    <main className="list-screen">
      <header className="list-header">
        <h1>気になるものリスト</h1>

        <div className="header-actions">
          <button type="button" onClick={onCreate}>
            新規登録
          </button>
        </div>
      </header>

      <h2 className="screen-title">一覧画面</h2>

      <div className="filters">
        <label>
          カテゴリ
          <select
            value={selectedCategory}
            onChange={(event) =>
              setSelectedCategory(event.target.value)
            }
          >
            <option value="すべて">すべて</option>
            {categories.map((value) => (
              <option key={value} value={value}>
                {value}
              </option>
            ))}
          </select>
        </label>

        <label>
          ステータス
          <select
            value={selectedStatus}
            onChange={(event) =>
              setSelectedStatus(event.target.value)
            }
          >
            <option value="すべて">すべて</option>
            {statuses.map((value) => (
              <option key={value} value={value}>
                {value}
              </option>
            ))}
          </select>
        </label>
      </div>

      <table>
        <thead>
          <tr>
            <th>名前</th>
            <th>カテゴリ</th>
            <th>ステータス</th>
            <th>価格</th>
            <th>メモ</th>
            <th>操作</th>
          </tr>
        </thead>

        <tbody>
          {visibleItems.map((item) => (
            <tr key={item.id}>
              <td>{item.name}</td>
              <td>{item.category}</td>
              <td>{item.status}</td>
              <td>{item.price.toLocaleString()}円</td>
              <td>{item.memo}</td>
              <td>
                <button
                  type="button"
                  onClick={() => onEdit(item.id)}
                >
                  編集
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>

      {visibleItems.length === 0 && (
        <p>条件に合うデータがありません。</p>
      )}
    </main>
  );
}

アプリの完成状況

  • ✅ Reactを動かし、JSXを表示する
  • ✅ Stateで画面を切り替える
  • ✅ 1件のデータを表示する
  • ✅ 複数のデータを一覧表示する
  • ✅ コンポーネントとpropsを使う
  • ✅ コンポーネントを別ファイルへ分ける
  • ✅ 新規登録画面を作る
  • ✅ 一覧データをStateで管理する
  • ✅ 編集画面を作る
  • ✅ データを更新する
  • ✅ データを削除する
  • ✅ カテゴリ・ステータスで絞り込む
  • ⬜ キーワードで検索する
  • ⬜ localStorageで保存して完成
スポンサーリンク