今回やること
前回までで、
気になるものを追加・更新・削除できるようになりました。
今回は一覧画面にカテゴリとステータスの選択欄を追加し、
選んだ条件に合うデータだけを表示できるようにします。
カテゴリやステータスを選ぶ
↓
選択した値をStateで管理する
↓
itemsから条件に合うデータだけを取り出す
↓
表示用のvisibleItemsを作る
↓
visibleItemsを一覧へ表示する
今回は、一覧データそのものを変更するわけではありません。
もとのitemsはそのまま残し、条件に合うデータだけを一覧に表示します。
カテゴリとステータスの選択肢を読み込む
今回は、
第7回でoptions.jsへまとめた
カテゴリとステータスの選択肢を一覧画面でも使います。
まずは、
src/components/ListScreen.jsxを開きます。
ファイルの先頭で、
useStateと選択肢を読み込んでください。
import { useState } from "react";
import { categories, statuses } from "../options";
categoriesとstatusesは、
新規登録画面や編集画面でも使っている同じ配列です。
選択した値をStateで管理する
次に、
現在選択しているカテゴリとステータスを覚えておくStateを用意します。
ListScreenコンポーネントの先頭に、
次の2つのStateを追加してください。
const [selectedCategory, setSelectedCategory] = useState("すべて");
const [selectedStatus, setSelectedStatus] = useState("すべて");
selectedCategoryには現在選んでいるカテゴリ、
selectedStatusには現在選んでいるステータスが入ります。
最初はどちらも絞り込まないため、
初期値を"すべて"にしています。
カテゴリとステータスの選択欄を追加する
続いて、一覧画面へ2つのselectを追加します。
src/components/ListScreen.jsxの
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>
どちらのselectにも、
絞り込みをしないための「すべて」を最初に追加しています。
その下には、
categoriesとstatusesを
map()で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.categoryが
selectedCategoryと一致するデータだけを残します。
ステータスも同じ考え方です。
カテゴリ:本
ステータス:気になる
↓
categoryが「本」
かつ
statusが「気になる」
↓
両方の条件に合うデータだけ残る
2つのfilter()を順番に使うことで、
カテゴリとステータスの両方を組み合わせて絞り込めます。
visibleItemsを一覧に表示する
条件に合うデータを
visibleItemsとして作れるようになりました。
ListScreenコンポーネントのtbodyの中にある、次の部分を探してください。
{items.map((item) => (
このitemsを
visibleItemsへ変更します。
{visibleItems.map((item) => (
これで、
一覧に表示されるのは
visibleItemsに残っているデータだけになります。
ファイルを保存して、
カテゴリやステータスを選んでみましょう。
選んだ条件に合わせて一覧が変わればOKです。
また、「すべて」に戻すと、
その条件での絞り込みが解除されることも確認してください。
visibleItemsはStateにしない
今回は、
selectedCategoryと
selectedStatusは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.lengthが0の場合だけ、メッセージを表示します。
ブラウザで確認する
最後に、いくつか条件を変えて動きを確認してみましょう。
- カテゴリだけを選ぶと、そのカテゴリのデータだけが表示される
- ステータスだけを選ぶと、そのステータスのデータだけが表示される
- 両方を選ぶと、2つの条件に合うデータだけが表示される
- 「すべて」に戻すと、その条件での絞り込みが解除される
- 条件に合うデータがない場合はメッセージが表示される
すべて確認できれば、
カテゴリとステータスによる絞り込みは完成です。
ReactとJavaScriptを分けて見てみよう
今回登場した内容を、
ReactとJavaScriptに分けて整理します。
今回登場したReactとJavaScriptの要素
| 種類 |
今回登場したもの |
| React |
State、更新関数、Stateの更新による再レンダリング |
| JavaScript |
filter()、map()、条件式 |
選択したカテゴリやステータスを
Stateとして管理する部分はReactです。
一方、
filter()を使って
条件に合うデータだけを取り出す部分はJavaScriptです。
Stateが変わると再レンダリングされ、
新しい条件でvisibleItemsが計算されることで
一覧の表示も変わります。
今回のまとめ
- 一覧画面にカテゴリとステータスの選択欄を追加した
options.jsの選択肢を一覧画面でも再利用した
- 選択した値を
selectedCategoryとselectedStatus 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で保存して完成