- DAY 26:Stateへの追加 前半 ― 「入力したものが画面に並ぶ」までの一本の流れを作る
- 入力 → State ― 「フォームの中身をStateで持つ」
- State → 配列に追加 ― 「1つの文字列を、一覧の1要素にする」
- 配列に追加 → map → 画面表示 ― 「一覧として並べる」
- 実践:買い物リストを少しだけ実用的にする
- 練習用テンプレート:DAY 26 前半の完成版「買い物リスト」
- DAY 26 前半のまとめ
- DAY 26:Stateへの追加 中盤 ― 「使える買い物リスト」に育てながら、State追加の感覚を深める
- 入力 → State ― 「フォーム入力を丁寧に扱う」
- State → 配列に追加 ― 「文字列ではなくオブジェクトとして追加する」
- 配列に追加 → map → 画面表示 ― 「オブジェクト配列をJSXに変換する」
- 実践:買い物リストに「削除」と「購入済みフラグ」を追加する
- 練習用テンプレート:DAY 26 中盤版「買い物リスト」
- DAY 26 中盤のまとめ
- DAY 26:Stateへの追加 後半 ― 「入力から一覧表示まで」を“自分の手でコントロールできる”ようになる
- 入力 → State ― 「ユーザーの入力をプログラムの世界に持ち込む」
- State → 配列に追加 ― 「1件のデータ」を意味のある形で扱う
- 配列に追加 → map ― 「配列Stateを画面に変換する」
- map → 画面表示 ― 「Stateの変化がそのままUIに反映される」
- 実践:削除と購入済みフラグを追加して、State更新を仕上げる
- 練習用テンプレート:DAY 26 後半版「買い物リスト」完成コード
- DAY 26 後半のまとめ ― 「一本の流れ」を自分の中に定着させる
DAY 26:Stateへの追加 前半 ― 「入力したものが画面に並ぶ」までの一本の流れを作る
DAY 26では、いよいよ 「入力 → State → 配列に追加 → map → 画面表示」 という、Reactアプリの王道パターンを実践していきます。
題材はとても身近な 「買い物リスト」 です。
- テキスト入力欄に商品名を入力する
- ボタンを押してリストに追加する
- 追加された商品が画面に一覧表示される
という流れを、ステップバイステップで組み立てていきます。
入力 → State ― 「フォームの中身をStateで持つ」
入力欄の値をStateで管理する
まずは、「入力した文字」をStateで管理するところから始めます。
Reactでは、フォームの入力欄(<input>)の値を Stateとして持つのが基本パターンです。
import { useState } from "react";
function ShoppingList() {
const [itemName, setItemName] = useState("");
return (
<div>
<h2>買い物リスト</h2>
<input
type="text"
value={itemName}
onChange={(e) => setItemName(e.target.value)}
placeholder="商品名を入力してください"
/>
</div>
);
}
export default ShoppingList;
JSXここで起きていることは、
itemNameが「今入力欄に入っている文字」を表すStatesetItemNameが「入力欄の文字を更新するための関数」value={itemName}で、「Stateの値を入力欄に反映」onChangeで、「入力された文字をStateに反映」
という流れです。
重要ポイント:
- 入力欄の値は「ただの変数」ではなく、「State」として持つのがReactの基本です。
- これにより、「入力内容を使って何かする」ことが簡単になります。
State → 配列に追加 ― 「1つの文字列を、一覧の1要素にする」
買い物リストを配列Stateで持つ
次に、「買い物リストそのもの」を配列Stateで持ちます。
import { useState } from "react";
function ShoppingList() {
const [itemName, setItemName] = useState("");
const [items, setItems] = useState([]);
return (
<div>
<h2>買い物リスト</h2>
<input
type="text"
value={itemName}
onChange={(e) => setItemName(e.target.value)}
placeholder="商品名を入力してください"
/>
</div>
);
}
export default ShoppingList;
JSXここでの items は、
- 「買い物リストに追加された商品名の一覧」を表す配列State
です。
ボタンを押したときに配列に追加する
次に、「追加」ボタンを作り、 入力された商品名を items に追加します。
function ShoppingList() {
const [itemName, setItemName] = useState("");
const [items, setItems] = useState([]);
const handleAddItem = () => {
if (itemName.trim() === "") {
return; // 空文字は追加しない
}
setItems((prevItems) => [...prevItems, itemName]);
setItemName(""); // 追加後に入力欄を空にする
};
return (
<div>
<h2>買い物リスト</h2>
<input
type="text"
value={itemName}
onChange={(e) => setItemName(e.target.value)}
placeholder="商品名を入力してください"
/>
<button onClick={handleAddItem}>追加</button>
</div>
);
}
JSXここでの流れは、
- ユーザーが入力欄に商品名を入力する
itemNameにその文字が入る- 「追加」ボタンを押すと
handleAddItemが呼ばれる setItems((prevItems) => [...prevItems, itemName])で- 今の
itemsを展開し - その末尾に
itemNameを追加した 新しい配列 を作る
- 今の
- その新しい配列をStateにセットすることで、買い物リストが更新される
- 最後に
setItemName("")で入力欄をリセットする
重要ポイント:
- 「入力された1つの値」を、「一覧の1要素」として配列Stateに追加しています。
- ここでも、「直接変更せず、新しい配列を作る」というルールを守っています。
配列に追加 → map → 画面表示 ― 「一覧として並べる」
itemsを画面に表示する
次に、items を画面に一覧表示します。
function ShoppingList() {
const [itemName, setItemName] = useState("");
const [items, setItems] = useState([]);
const handleAddItem = () => {
if (itemName.trim() === "") {
return;
}
setItems((prevItems) => [...prevItems, itemName]);
setItemName("");
};
return (
<div>
<h2>買い物リスト</h2>
<input
type="text"
value={itemName}
onChange={(e) => setItemName(e.target.value)}
placeholder="商品名を入力してください"
/>
<button onClick={handleAddItem}>追加</button>
<ul style={{ marginTop: "8px" }}>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
JSXここでの items.map(...) は、
itemsの各要素(商品名)を<li>要素に変換して<ul>の中に並べる
という処理です。
重要ポイント:
- 「配列State → map → JSX → 画面表示」という流れが、Reactの一覧表示の基本です。
- 入力 → State → 配列に追加 → map → 画面表示、という一本の線がつながりました。
実践:買い物リストを少しだけ実用的にする
重複を許すかどうか、空文字をどう扱うか
実際のアプリを意識すると、 少しだけ気をつけたいポイントがあります。
- 空文字は追加しない
- スペースだけの入力も追加しない
- 重複は許してもよい(買うものが同じでも問題ない)
これらは、handleAddItem の中で簡単に制御できます。
const handleAddItem = () => {
const trimmed = itemName.trim();
if (trimmed === "") {
return;
}
setItems((prevItems) => [...prevItems, trimmed]);
setItemName("");
};
JSX削除ボタンを付けてみる
前半の仕上げとして、 各商品に「削除」ボタンを付けてみます。
function ShoppingList() {
const [itemName, setItemName] = useState("");
const [items, setItems] = useState([]);
const handleAddItem = () => {
const trimmed = itemName.trim();
if (trimmed === "") {
return;
}
setItems((prevItems) => [...prevItems, trimmed]);
setItemName("");
};
const handleDeleteItem = (indexToDelete) => {
setItems((prevItems) =>
prevItems.filter((_, index) => index !== indexToDelete)
);
};
return (
<div>
<h2>買い物リスト</h2>
<input
type="text"
value={itemName}
onChange={(e) => setItemName(e.target.value)}
placeholder="商品名を入力してください"
/>
<button onClick={handleAddItem}>追加</button>
<ul style={{ marginTop: "8px" }}>
{items.map((item, index) => (
<li key={index}>
{item}
<button
style={{ marginLeft: "8px" }}
onClick={() => handleDeleteItem(index)}
>
削除
</button>
</li>
))}
</ul>
</div>
);
}
JSXここでのポイントは、
- 削除も「新しい配列を作る」形で書いていること
filter()を使って、「削除したい要素以外」を残した新しい配列を作っていること
です。
練習用テンプレート:DAY 26 前半の完成版「買い物リスト」
最後に、DAY 26前半の内容をまとめた 「買い物リスト」コンポーネントの完成版を示します。
import { useState } from "react";
function ShoppingList() {
const [itemName, setItemName] = useState("");
const [items, setItems] = useState([]);
const handleAddItem = () => {
const trimmed = itemName.trim();
if (trimmed === "") {
return;
}
setItems((prevItems) => [...prevItems, trimmed]);
setItemName("");
};
const handleDeleteItem = (indexToDelete) => {
setItems((prevItems) =>
prevItems.filter((_, index) => index !== indexToDelete)
);
};
return (
<div>
<h2>買い物リスト</h2>
<input
type="text"
value={itemName}
onChange={(e) => setItemName(e.target.value)}
placeholder="商品名を入力してください"
/>
<button onClick={handleAddItem}>追加</button>
<ul style={{ marginTop: "8px" }}>
{items.map((item, index) => (
<li key={index}>
{item}
<button
style={{ marginLeft: "8px" }}
onClick={() => handleDeleteItem(index)}
>
削除
</button>
</li>
))}
</ul>
</div>
);
}
export default ShoppingList;
JSXこの中には、
- 入力 → State:
itemNameで入力欄の値を管理 - State → 配列に追加:
setItemsで新しい配列を作って追加 - 配列 → map → 画面表示:
items.map(...)で一覧表示 - Stateを直接変更しない:
prevItemsを直接触らず、[...]やfilter()で新しい配列を作る
というDAY 26前半の学習項目が、すべて自然な形で含まれています。
DAY 26 前半のまとめ
DAY 26の前半では、
- 「入力欄の値をStateで管理する」ことで、ユーザーの入力をReactの世界に取り込む感覚を掴んだこと
- 「入力された1つの値を、配列Stateの1要素として追加する」流れを、Spread構文を使って実装したこと
- 「配列State → map → JSX → 画面表示」という、Reactの一覧表示の基本パターンを、買い物リストを通して体験したこと
- 削除機能を通して、「追加も削除も、新しい配列を作ってStateを更新する」というルールを再確認したこと
が大きなポイントになります。
ここまで来ると、
- 「入力 → State → 配列に追加 → map → 画面表示」
という一本の流れが、自分の中でかなりはっきり見えているはずです。 中盤以降では、この流れに「バリデーション」「ID管理」「複数フィールドの入力」などを加え、 より本格的なフォーム+一覧のアプリへと発展させていきます。
DAY 26:Stateへの追加 中盤 ― 「使える買い物リスト」に育てながら、State追加の感覚を深める
DAY 26の中盤では、前半で作った 「入力 → State → 配列に追加 → map → 画面表示」 という流れを、もう一歩「実際に使えるアプリ」に近づけていきます。
題材は引き続き 買い物リスト ですが、
- 入力の扱い方を少し丁寧にする
- 「1件のデータ」をただの文字列ではなく「オブジェクト」として扱う
- 将来の拡張(数量・メモなど)を見据えた形にする
という視点で、Stateへの追加を深掘りしていきます。
入力 → State ― 「フォーム入力を丁寧に扱う」
入力欄を「ただの文字」ではなく「意味のあるデータ」として見る
前半では、商品名を1つの文字列としてStateに持ちました。
const [itemName, setItemName] = useState("");
JSX中盤では、この「入力欄の値」をもう少し丁寧に扱います。
- 空文字やスペースだけの入力は追加しない
- 入力後は必ずクリアする
- 将来「数量」や「メモ」を追加したくなったときに、拡張しやすい形にしておく
という意識を持つと、フォームの扱い方が一段レベルアップします。
まずは、商品名だけのシンプルな形で整理します。
import { useState } from "react";
function ShoppingList() {
const [itemName, setItemName] = useState("");
const handleChangeItemName = (e) => {
setItemName(e.target.value);
};
return (
<div>
<h2>買い物リスト</h2>
<input
type="text"
value={itemName}
onChange={handleChangeItemName}
placeholder="商品名を入力してください"
/>
</div>
);
}
export default ShoppingList;
JSXここでのポイントは、
handleChangeItemNameという関数を用意して、 入力処理を分けていること- 将来、入力値に対してバリデーションや整形処理を入れたくなったときに、 この関数の中で対応できるようにしていること
です。
State → 配列に追加 ― 「文字列ではなくオブジェクトとして追加する」
1件のデータを「オブジェクト」で表現する
前半では、items を「文字列の配列」として扱いました。
const [items, setItems] = useState([]);
// 例: ["牛乳", "パン", "卵"]
JSX中盤では、これを 「オブジェクトの配列」 に変えていきます。
const [items, setItems] = useState([]);
// 例: [{ id: 1, name: "牛乳" }, { id: 2, name: "パン" }]
JSXこうしておくと、
- 将来「数量」「メモ」「購入済みフラグ」などを追加しやすい
keyにidを使える- 「1件のデータ」を1つのまとまりとして扱える
というメリットがあります。
追加処理をオブジェクトベースにする
では、追加処理をオブジェクトベースに書き換えてみます。
function ShoppingList() {
const [itemName, setItemName] = useState("");
const [items, setItems] = useState([]);
const [nextId, setNextId] = useState(1);
const handleAddItem = () => {
const trimmed = itemName.trim();
if (trimmed === "") {
return;
}
const newItem = {
id: nextId,
name: trimmed,
};
setItems((prevItems) => [...prevItems, newItem]);
setNextId((prevId) => prevId + 1);
setItemName("");
};
// ...
}
JSXここで起きていることは、
- 入力された商品名を
trim()して、前後のスペースを除去する - 空文字の場合は何もせずに終了する(バリデーション)
newItemとして「1件の買い物アイテム」をオブジェクトで作るsetItems((prevItems) => [...prevItems, newItem])で- 今の配列を展開し
- 末尾に
newItemを追加した 新しい配列 を作る
nextIdをインクリメントして、次の追加に備える- 入力欄をクリアする
重要ポイント:
- 「1件のデータ」をオブジェクトとして扱うことで、 配列Stateが「意味のある一覧」になります。
- Stateへの追加は、「新しいオブジェクトを作って、新しい配列に足す」という流れで考えます。
配列に追加 → map → 画面表示 ― 「オブジェクト配列をJSXに変換する」
オブジェクト配列をmapで表示する
items がオブジェクト配列になったので、 表示部分もそれに合わせて書き換えます。
return (
<div>
<h2>買い物リスト</h2>
<input
type="text"
value={itemName}
onChange={(e) => setItemName(e.target.value)}
placeholder="商品名を入力してください"
/>
<button onClick={handleAddItem}>追加</button>
<ul style={{ marginTop: "8px" }}>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
JSXここでの items.map((item) => ...) は、
itemが{ id, name }というオブジェクトitem.nameを画面に表示key={item.id}で、ReactにとってのIDを渡している
という形になっています。
重要ポイント:
- 「オブジェクト配列+map+key」は、Reactのリスト表示の王道パターンです。
- Stateへの追加でオブジェクトを使うことで、表示側も自然にオブジェクトベースになります。
実践:買い物リストに「削除」と「購入済みフラグ」を追加する
中盤では、買い物リストを少しだけ実用的にしてみます。
削除機能をオブジェクトベースで実装する
まずは削除機能です。
const handleDeleteItem = (idToDelete) => {
setItems((prevItems) =>
prevItems.filter((item) => item.id !== idToDelete)
);
};
JSX表示側では、各行に削除ボタンを付けます。
<ul style={{ marginTop: "8px" }}>
{items.map((item) => (
<li key={item.id}>
{item.name}
<button
style={{ marginLeft: "8px" }}
onClick={() => handleDeleteItem(item.id)}
>
削除
</button>
</li>
))}
</ul>
JSXここでのポイントは、
- 削除も「新しい配列を作る」形で書いていること
filter()を使って、「削除したいアイテム以外」を残した新しい配列を作っていること
です。
購入済みフラグを追加する
次に、「購入済みフラグ」を追加してみます。
まず、items の初期値と追加処理を変更します。
const [items, setItems] = useState([]);
const handleAddItem = () => {
const trimmed = itemName.trim();
if (trimmed === "") {
return;
}
const newItem = {
id: nextId,
name: trimmed,
bought: false, // 購入済みフラグ
};
setItems((prevItems) => [...prevItems, newItem]);
setNextId((prevId) => prevId + 1);
setItemName("");
};
JSX次に、「購入済みフラグを切り替える」処理を書きます。
const handleToggleBought = (id) => {
setItems((prevItems) =>
prevItems.map((item) =>
item.id === id ? { ...item, bought: !item.bought } : item
)
);
};
JSX表示側では、購入済みフラグに応じて表示を変えます。
<ul style={{ marginTop: "8px" }}>
{items.map((item) => (
<li key={item.id}>
<button onClick={() => handleToggleBought(item.id)}>
{item.bought ? "✅" : "⬜️"}
</button>
<span style={{ marginLeft: "8px", marginRight: "8px" }}>
{item.name}
</span>
<button onClick={() => handleDeleteItem(item.id)}>削除</button>
</li>
))}
</ul>
JSX重要ポイント:
- 「購入済みフラグ」のような状態も、オブジェクトのプロパティとしてStateに持ちます。
- 更新は
map()+オブジェクトSpread({ ...item, bought: !item.bought })で 「一部だけ変えた新しいオブジェクト」を作る形で行います。
練習用テンプレート:DAY 26 中盤版「買い物リスト」
最後に、DAY 26中盤の内容をまとめた 「買い物リスト」コンポーネントのテンプレートを示します。
import { useState } from "react";
function ShoppingList() {
const [itemName, setItemName] = useState("");
const [items, setItems] = useState([]);
const [nextId, setNextId] = useState(1);
const handleAddItem = () => {
const trimmed = itemName.trim();
if (trimmed === "") {
return;
}
const newItem = {
id: nextId,
name: trimmed,
bought: false,
};
setItems((prevItems) => [...prevItems, newItem]);
setNextId((prevId) => prevId + 1);
setItemName("");
};
const handleDeleteItem = (idToDelete) => {
setItems((prevItems) =>
prevItems.filter((item) => item.id !== idToDelete)
);
};
const handleToggleBought = (id) => {
setItems((prevItems) =>
prevItems.map((item) =>
item.id === id ? { ...item, bought: !item.bought } : item
)
);
};
return (
<div>
<h2>買い物リスト(Stateへの追加 中盤)</h2>
<input
type="text"
value={itemName}
onChange={(e) => setItemName(e.target.value)}
placeholder="商品名を入力してください"
/>
<button onClick={handleAddItem}>追加</button>
<ul style={{ marginTop: "8px" }}>
{items.map((item) => (
<li key={item.id}>
<button onClick={() => handleToggleBought(item.id)}>
{item.bought ? "✅" : "⬜️"}
</button>
<span style={{ marginLeft: "8px", marginRight: "8px" }}>
{item.name}
</span>
<button onClick={() => handleDeleteItem(item.id)}>削除</button>
</li>
))}
</ul>
</div>
);
}
export default ShoppingList;
JSXこのテンプレートの中には、
- 入力 → State:
itemNameでフォーム入力を管理 - State → 配列に追加:
newItemオブジェクトをitemsに追加 - 配列 → map → 画面表示:オブジェクト配列を
map()でJSXに変換 - Stateを直接変更しない:
prevItemsを直接触らず、[...]/filter()/map()で新しい配列を作る - Spread構文:オブジェクトの更新に
{ ...item, bought: !item.bought }を使用
というDAY 26中盤の学習項目が、すべて自然な形で含まれています。
DAY 26 中盤のまとめ
DAY 26の中盤では、
- 入力欄の値を丁寧に扱い、バリデーションや拡張を意識した形でStateに持つ感覚を育てたこと
- 「1件のデータ」をオブジェクトとして扱い、配列Stateを「意味のある一覧」として設計したこと
- オブジェクト配列+
map+keyという、実務でもそのまま使えるリスト表示の形を確認したこと - 削除・購入済みフラグの追加を通して、 「新しい配列を作る」「Stateを直接変更しない」「Spread構文で一部だけ更新する」 というルールを、より実践的なコードの中で体に馴染ませたこと
が大きなポイントになります。
この段階まで来ると、
- 「入力 → State → オブジェクトとして配列に追加 → map → 画面表示」
という流れが、かなりはっきりと自分の中に定着しているはずです。 後半では、複数入力フィールドやバリデーション、エラーメッセージなどを組み合わせて、 より本格的なフォーム+一覧アプリへと発展させていきます。
DAY 26:Stateへの追加 後半 ― 「入力から一覧表示まで」を“自分の手でコントロールできる”ようになる
DAY 26の後半では、 入力 → State → 配列に追加 → map → 画面表示 というReactの王道パターンを、「なんとなく分かる」から「自分で設計できる」レベルまで引き上げていきます。
題材は引き続き 買い物リスト です。 ここでは、完成形のコードをただ書くだけではなく、
- なぜその書き方になるのか
- どこが重要なポイントなのか
- どこを間違えるとバグやセキュリティ的な問題につながるのか
まで、丁寧に言語化していきます。
入力 → State ― 「ユーザーの入力をプログラムの世界に持ち込む」
入力欄は“ただのHTML”ではなく“ReactのStateの入り口”
まず、買い物リストの入り口になる「商品名の入力欄」を考えます。
import { useState } from "react";
function ShoppingList() {
const [itemName, setItemName] = useState("");
return (
<div>
<h2>買い物リスト</h2>
<input
type="text"
value={itemName}
onChange={(e) => setItemName(e.target.value)}
placeholder="商品名を入力してください"
/>
</div>
);
}
export default ShoppingList;
JSXここで起きていることを、少し深く分解してみます。
itemName- 「今入力欄に入っている文字」を表す 状態(State) です。
- 画面に表示されている値と、1対1で対応しています。
value={itemName}- 「Stateの値を入力欄に反映する」という意味です。
- これにより、Reactが入力欄の中身を完全にコントロールできるようになります。
onChange={(e) => setItemName(e.target.value)}- ユーザーが入力した瞬間にイベントが発生し、
e.target.value(入力された文字列)がitemNameに保存されます。 - 「ユーザーの入力 → State」という橋渡しをしている部分です。
- ユーザーが入力した瞬間にイベントが発生し、
重要な本質:
入力欄は、ユーザーの世界からReactの世界への“入り口”です。 その入り口をStateでしっかり受け止めることで、 入力内容を自由に加工・検証・保存できるようになります。
State → 配列に追加 ― 「1件のデータ」を意味のある形で扱う
配列Stateは「画面に出ている一覧そのもの」
次に、「買い物リストそのもの」をStateで持ちます。
function ShoppingList() {
const [itemName, setItemName] = useState("");
const [items, setItems] = useState([]);
const [nextId, setNextId] = useState(1);
// ...
}
JSXここでの items は、
- 「今画面に表示されている買い物リストの一覧」を表す配列Stateです。
- つまり、UIの状態そのものです。
文字列ではなくオブジェクトとして追加する
後半では、「1件の買い物アイテム」をオブジェクトとして扱います。
const handleAddItem = () => {
const trimmed = itemName.trim();
if (trimmed === "") {
// 空文字やスペースだけの入力は追加しない
return;
}
const newItem = {
id: nextId,
name: trimmed,
bought: false,
};
setItems((prevItems) => [...prevItems, newItem]);
setNextId((prevId) => prevId + 1);
setItemName("");
};
JSXここでの流れをステップバイステップで追ってみます。
- 入力値の整形とバリデーション
itemName.trim()で前後のスペースを削除します。- 空文字の場合は
returnで処理を中断し、 「意味のないデータ」をStateに入れないようにしています。 - これは、セキュリティや品質の観点でも非常に重要です。
newItemオブジェクトの作成id: 一意な識別子。nextIdを使って連番にしています。name: 整形済みの商品名。bought: 購入済みフラグ。初期値はfalse。
- 配列Stateへの追加
setItems((prevItems) => [...prevItems, newItem])ここが「Stateへの追加」の核心です。prevItemsは「今の買い物リストの配列」ですが、 直接書き換えず、[...](Spread構文)でコピーしています。- その末尾に
newItemを追加し、新しい配列を作ってからStateに渡しています。
- IDの更新と入力欄のクリア
setNextId((prevId) => prevId + 1)で次のIDを準備します。setItemName("")で入力欄を空にし、連続入力しやすくしています。
ここで深掘りしたいポイント:
- Stateを直接変更しない理由
items.push(newItem)のように直接変更すると、 Reactが「前回と今回のStateの違い」を正しく検知できないことがあります。- その結果、画面が更新されなかったり、予期しない挙動になったりします。
- 不変性(immutability)を守ることは、バグ防止とセキュリティの観点からも重要です。
- Spread構文の役割
[...]は「元の配列の中身をコピーして、新しい配列を作る」ための記法です。- 元の配列を壊さず、新しい配列を作ることで、Reactの仕組みと整合性が取れます。
配列に追加 → map ― 「配列Stateを画面に変換する」
mapは「配列 → JSX」の変換器
次に、items を画面に表示します。
<ul style={{ marginTop: "8px" }}>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
JSXここで起きていることは、
items.map((item) => ...)で、 配列の各要素(item)を<li>要素に変換しています。key={item.id}で、Reactにとっての「その行のID」を渡しています。
重要な本質:
Reactの画面は、「Stateの現在値」をそのまま映しているだけです。 mapは、「配列State → JSX → 画面」という橋渡しをしている関数です。
なぜ key が重要なのか
key は、Reactが「どの要素がどれか」を識別するためのIDです。
- 要素の追加・削除・並び替えが起きたとき、 Reactは
keyを手がかりに「どの要素が変わったか」を判断します。 indexをkeyにすると、並び替えや削除のときに誤認識が起きることがあります。- そのため、今回のように
idを持たせてkey={item.id}とするのが安全です。
map → 画面表示 ― 「Stateの変化がそのままUIに反映される」
画面は“結果”であり、“操作対象”ではない
Reactでは、
- 画面を直接操作するのではなく、
- Stateを更新することで、
- 画面が自動的に新しい状態を映す
という考え方をします。
買い物リストの場合、
- 商品を追加する →
itemsに新しい要素が入る - Reactが再レンダリングする →
items.map(...)が再評価される - 新しい一覧が画面に表示される
という流れです。
ここがReactの気持ちよさ:
「画面をどう変えるか」ではなく、「Stateをどう変えるか」を考える。 そうすると、UIの複雑さがぐっと減り、 ロジックの安全性も高まります。
実践:削除と購入済みフラグを追加して、State更新を仕上げる
削除機能 ― filterで「残したいものだけ」の新しい配列を作る
削除機能を追加してみます。
const handleDeleteItem = (idToDelete) => {
setItems((prevItems) =>
prevItems.filter((item) => item.id !== idToDelete)
);
};
JSX表示側では、各行に削除ボタンを付けます。
<ul style={{ marginTop: "8px" }}>
{items.map((item) => (
<li key={item.id}>
{item.name}
<button
style={{ marginLeft: "8px" }}
onClick={() => handleDeleteItem(item.id)}
>
削除
</button>
</li>
))}
</ul>
JSXここでのポイント:
filter()は「残したいものだけを集めた新しい配列」を作るメソッドです。- 元の配列を直接変更せず、新しい配列を返しているため、 不変性のルールを守っています。
購入済みフラグ ― map+オブジェクトSpreadで一部だけ更新する
次に、「購入済みフラグ」を追加します。
const handleToggleBought = (id) => {
setItems((prevItems) =>
prevItems.map((item) =>
item.id === id ? { ...item, bought: !item.bought } : item
)
);
};
JSX表示側では、フラグに応じて表示を変えます。
<ul style={{ marginTop: "8px" }}>
{items.map((item) => (
<li key={item.id}>
<button onClick={() => handleToggleBought(item.id)}>
{item.bought ? "✅" : "⬜️"}
</button>
<span style={{ marginLeft: "8px", marginRight: "8px" }}>
{item.name}
</span>
<button onClick={() => handleDeleteItem(item.id)}>削除</button>
</li>
))}
</ul>
JSXここで深掘りしたいポイント:
map()は「配列の各要素を変換して、新しい配列を作る」メソッドです。- 対象の要素だけ
{ ...item, bought: !item.bought }で 「コピー+一部変更」した新しいオブジェクトを作っています。 - 元の
itemオブジェクトは壊さず、新しいオブジェクトを返しているため、 不変性が保たれています。
練習用テンプレート:DAY 26 後半版「買い物リスト」完成コード
import { useState } from "react";
function ShoppingList() {
const [itemName, setItemName] = useState("");
const [items, setItems] = useState([]);
const [nextId, setNextId] = useState(1);
const handleAddItem = () => {
const trimmed = itemName.trim();
if (trimmed === "") {
return;
}
const newItem = {
id: nextId,
name: trimmed,
bought: false,
};
setItems((prevItems) => [...prevItems, newItem]);
setNextId((prevId) => prevId + 1);
setItemName("");
};
const handleDeleteItem = (idToDelete) => {
setItems((prevItems) =>
prevItems.filter((item) => item.id !== idToDelete)
);
};
const handleToggleBought = (id) => {
setItems((prevItems) =>
prevItems.map((item) =>
item.id === id ? { ...item, bought: !item.bought } : item
)
);
};
return (
<div>
<h2>買い物リスト(Stateへの追加 後半)</h2>
<input
type="text"
value={itemName}
onChange={(e) => setItemName(e.target.value)}
placeholder="商品名を入力してください"
/>
<button onClick={handleAddItem}>追加</button>
<ul style={{ marginTop: "8px" }}>
{items.map((item) => (
<li key={item.id}>
<button onClick={() => handleToggleBought(item.id)}>
{item.bought ? "✅" : "⬜️"}
</button>
<span style={{ marginLeft: "8px", marginRight: "8px" }}>
{item.name}
</span>
<button onClick={() => handleDeleteItem(item.id)}>削除</button>
</li>
))}
</ul>
</div>
);
}
export default ShoppingList;
JSXDAY 26 後半のまとめ ― 「一本の流れ」を自分の中に定着させる
DAY 26の後半で押さえてほしいのは、次の一本の流れです。
入力 → State → 配列に追加 → map → 画面表示
そして、その裏側にある考え方です。
- 入力欄の値は必ずStateで管理する
- 追加するデータはオブジェクトとして扱うと拡張しやすい
- 配列Stateへの追加・削除・更新は、 必ず新しい配列を作る(不変性)
- Spread構文は「コピー+一部変更」のための基本ツール
- mapは「配列State → JSX → 画面」の変換器
- keyは「Reactが要素を識別するためのID」であり、
idを使うのが安全
この流れと考え方が腹に落ちていると、 今後の「ユーザー一覧」「タスク管理」「コメント一覧」など、 あらゆる“一覧系UI”を自分の力で組み立てられるようになります。
