モダンJavaScriptの基礎から始める挫折しないためのReact入門 60日コース | Stateを使いこなす - DAY 26:Stateへの追加

React 60日で身につけるReact
スポンサーリンク
スポンサーリンク
  1. DAY 26:Stateへの追加 前半 ― 「入力したものが画面に並ぶ」までの一本の流れを作る
  2. 入力 → State ― 「フォームの中身をStateで持つ」
    1. 入力欄の値をStateで管理する
  3. State → 配列に追加 ― 「1つの文字列を、一覧の1要素にする」
    1. 買い物リストを配列Stateで持つ
    2. ボタンを押したときに配列に追加する
  4. 配列に追加 → map → 画面表示 ― 「一覧として並べる」
    1. itemsを画面に表示する
  5. 実践:買い物リストを少しだけ実用的にする
    1. 重複を許すかどうか、空文字をどう扱うか
    2. 削除ボタンを付けてみる
  6. 練習用テンプレート:DAY 26 前半の完成版「買い物リスト」
  7. DAY 26 前半のまとめ
  8. DAY 26:Stateへの追加 中盤 ― 「使える買い物リスト」に育てながら、State追加の感覚を深める
  9. 入力 → State ― 「フォーム入力を丁寧に扱う」
    1. 入力欄を「ただの文字」ではなく「意味のあるデータ」として見る
  10. State → 配列に追加 ― 「文字列ではなくオブジェクトとして追加する」
    1. 1件のデータを「オブジェクト」で表現する
    2. 追加処理をオブジェクトベースにする
  11. 配列に追加 → map → 画面表示 ― 「オブジェクト配列をJSXに変換する」
    1. オブジェクト配列をmapで表示する
  12. 実践:買い物リストに「削除」と「購入済みフラグ」を追加する
    1. 削除機能をオブジェクトベースで実装する
    2. 購入済みフラグを追加する
  13. 練習用テンプレート:DAY 26 中盤版「買い物リスト」
  14. DAY 26 中盤のまとめ
  15. DAY 26:Stateへの追加 後半 ― 「入力から一覧表示まで」を“自分の手でコントロールできる”ようになる
  16. 入力 → State ― 「ユーザーの入力をプログラムの世界に持ち込む」
    1. 入力欄は“ただのHTML”ではなく“ReactのStateの入り口”
  17. State → 配列に追加 ― 「1件のデータ」を意味のある形で扱う
    1. 配列Stateは「画面に出ている一覧そのもの」
    2. 文字列ではなくオブジェクトとして追加する
  18. 配列に追加 → map ― 「配列Stateを画面に変換する」
    1. mapは「配列 → JSX」の変換器
    2. なぜ key が重要なのか
  19. map → 画面表示 ― 「Stateの変化がそのままUIに反映される」
    1. 画面は“結果”であり、“操作対象”ではない
  20. 実践:削除と購入済みフラグを追加して、State更新を仕上げる
    1. 削除機能 ― filterで「残したいものだけ」の新しい配列を作る
    2. 購入済みフラグ ― map+オブジェクトSpreadで一部だけ更新する
  21. 練習用テンプレート:DAY 26 後半版「買い物リスト」完成コード
  22. 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 が「今入力欄に入っている文字」を表すState
  • setItemName が「入力欄の文字を更新するための関数」
  • 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

ここでの流れは、

  1. ユーザーが入力欄に商品名を入力する
  2. itemName にその文字が入る
  3. 「追加」ボタンを押すと handleAddItem が呼ばれる
  4. setItems((prevItems) => [...prevItems, itemName])
    • 今の items を展開し
    • その末尾に itemName を追加した 新しい配列 を作る
  5. その新しい配列をStateにセットすることで、買い物リストが更新される
  6. 最後に 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

この中には、

  • 入力 → StateitemName で入力欄の値を管理
  • 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

こうしておくと、

  • 将来「数量」「メモ」「購入済みフラグ」などを追加しやすい
  • keyid を使える
  • 「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

ここで起きていることは、

  1. 入力された商品名を trim() して、前後のスペースを除去する
  2. 空文字の場合は何もせずに終了する(バリデーション)
  3. newItem として「1件の買い物アイテム」をオブジェクトで作る
  4. setItems((prevItems) => [...prevItems, newItem])
    • 今の配列を展開し
    • 末尾に newItem を追加した 新しい配列 を作る
  5. nextId をインクリメントして、次の追加に備える
  6. 入力欄をクリアする

重要ポイント:

  • 「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

このテンプレートの中には、

  • 入力 → StateitemName でフォーム入力を管理
  • State → 配列に追加newItem オブジェクトを items に追加
  • 配列 → map → 画面表示:オブジェクト配列を map() でJSXに変換
  • Stateを直接変更しないprevItems を直接触らず、[...] / filter() / map() で新しい配列を作る
  • Spread構文:オブジェクトの更新に { ...item, bought: !item.bought } を使用

というDAY 26中盤の学習項目が、すべて自然な形で含まれています。

DAY 26 中盤のまとめ

DAY 26の中盤では、

  • 入力欄の値を丁寧に扱い、バリデーションや拡張を意識した形でStateに持つ感覚を育てたこと
  • 「1件のデータ」をオブジェクトとして扱い、配列Stateを「意味のある一覧」として設計したこと
  • オブジェクト配列+mapkey という、実務でもそのまま使えるリスト表示の形を確認したこと
  • 削除・購入済みフラグの追加を通して、 「新しい配列を作る」「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

ここでの流れをステップバイステップで追ってみます。

  1. 入力値の整形とバリデーション
    • itemName.trim() で前後のスペースを削除します。
    • 空文字の場合は return で処理を中断し、 「意味のないデータ」をStateに入れないようにしています。
    • これは、セキュリティや品質の観点でも非常に重要です。
  2. newItemオブジェクトの作成
    • id: 一意な識別子。nextId を使って連番にしています。
    • name: 整形済みの商品名。
    • bought: 購入済みフラグ。初期値は false
  3. 配列Stateへの追加
    • setItems((prevItems) => [...prevItems, newItem]) ここが「Stateへの追加」の核心です。
    • prevItems は「今の買い物リストの配列」ですが、 直接書き換えず、[...](Spread構文)でコピーしています。
    • その末尾に newItem を追加し、新しい配列を作ってからStateに渡しています。
  4. 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 を手がかりに「どの要素が変わったか」を判断します。
  • indexkey にすると、並び替えや削除のときに誤認識が起きることがあります。
  • そのため、今回のように 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;
JSX

DAY 26 後半のまとめ ― 「一本の流れ」を自分の中に定着させる

DAY 26の後半で押さえてほしいのは、次の一本の流れです。

入力 → State → 配列に追加 → map → 画面表示

そして、その裏側にある考え方です。

  • 入力欄の値は必ずStateで管理する
  • 追加するデータはオブジェクトとして扱うと拡張しやすい
  • 配列Stateへの追加・削除・更新は、 必ず新しい配列を作る(不変性)
  • Spread構文は「コピー+一部変更」のための基本ツール
  • mapは「配列State → JSX → 画面」の変換器
  • keyは「Reactが要素を識別するためのID」であり、id を使うのが安全

この流れと考え方が腹に落ちていると、 今後の「ユーザー一覧」「タスク管理」「コメント一覧」など、 あらゆる“一覧系UI”を自分の力で組み立てられるようになります。

タイトルとURLをコピーしました