blogreact006

砂糖は一粒ずつ——Stateで育てるインタラクション|第6回 React入門

著者: 管理者 / 2025-08-21 (更新: 2025-08-21)

導入

紅茶にお砂糖をひとつ、またひとつ——入れるたびに甘さが変わるように、アプリも「いま」の状態によって表情が変わります。
前回、ボタンを押す“出来事(イベント)”を学びました。今回は、その出来事に意味を与える「State(状態)」を、お嬢様と一緒に育ててまいりましょう☕🍬

前回の記事 ボタンが踊り出す!イベント処理|第5回 React入門ボタンが踊り出す!イベント処理|第5回 React入門


user01 Surprised
先生、前回のボタンは光って見えましたわ!
でも……押した回数を覚えていてほしい時、どういたしますの?
user02 Calm
良い着眼点です。
State を使うと「押した回数」のような“変化する情報”をコンポーネントに持たせられます。
イベントは火花、Stateはその火が灯るロウソクの炎だと思ってください。

1. Stateの第一歩:useStateで「数える」

📝 解説

  • State は「その瞬間のデータ」。コンポーネントの“記憶”です。
  • Reactでは useState というフックで状態を作ります。
  • useState(初期値)[値, 値を変える関数] を返します。
import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0); // 初期値は0

  function handleClick() {
    setCount((c) => c + 1); // 前の値から+1(安全な書き方)
  }

  return (
    <button onClick={handleClick}>
      いまのカウント: {count}
    </button>
  );
}

  • count は“いま”の数、setCount は“次の数に変えるレバー”。
  • イベント(クリック)→ setCount → UIが再描画、という流れです。

user01 Happy
まあ!砂糖を一粒ずつ増やすたびに、甘さ(表示)が変わりますのね✨
user02 Happy
そのとおり。StateはUIのソース・オブ・トゥルース(真実の源)。
表示は常にStateから計算されます。

2. 「直接書き換え」はご法度:不変性の礼儀作法

📝 解説

  • Stateは直接代入で書き換えてはいけません
    例)count = count + 1 は❌。必ず setCount を使います。

  • なぜ?——Reactは「Stateが変わった」という合図でUIを更新します。
    直接代入だと合図にならず、表示が変わらない恐れがあります。


user01 Serious
礼儀を守らないと、執事(React)が気づいてくださらないのですわね……。

3. 連続更新の落とし穴と“関数型更新”

📝 解説

  • 次のように書くと、意図通りに2回増えないことがあります。
setCount(count + 1);
setCount(count + 1); // 同じ"古い"countを参照してしまう場合がある
  • 解決策:関数型更新 で「前回の値」を受け取り、そこから計算します。
setCount((c) => c + 1);
setCount((c) => c + 1); // これで+2が確実に反映される
  • これなら、Reactの賢いまとめ(バッチ処理)があっても安全です。

user01 Surprised
まあ!前回の値を渡していただけるなんて、優雅なエスコートですこと💃

4. 入力欄は“コントロール”する:Controlled Components

📝 解説

  • フォームの入力はStateで管理するのが基本。

  • 入力欄の value をStateに結び、onChangeで更新します。

import { useState } from "react";

export default function NameForm() {
  const [name, setName] = useState("");

  function handleChange(e) {
    setName(e.target.value);
  }

  function handleSubmit(e) {
    e.preventDefault(); // ページ遷移を防ぐ
    alert(`ようこそ、${name}様☕`);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={handleChange} placeholder="お名前をどうぞ" />
      <button type="submit">ご入室</button>
    </form>
  );
}
  • これをコントロールド・コンポーネントと呼びます。
    UIの表示=name、更新=setName。常にStateが主役です。

user01 Calm
入力も衣装合わせのように、寸法(State)を基準に仕立てるのですわね👗

5. オブジェクトと配列のState——“広げる”作法

📝 解説

  • オブジェクトや配列を更新するときはコピーしてから変える(不変性)。

オブジェクトの例:

const [profile, setProfile] = useState({ name: "Alice", tea: "Earl Grey" });

function changeTea() {
  setProfile((p) => ({ ...p, tea: "Darjeeling" })); // スプレッドでコピー
}

配列の例:

const [items, setItems] = useState(["sugar", "spoon"]);

function addItem() {
  setItems((arr) => [...arr, "cookie"]);
}

function removeSpoon() {
  setItems((arr) => arr.filter((x) => x !== "spoon"));
}

function renameSugar() {
  setItems((arr) => arr.map((x) => (x === "sugar" ? "brown sugar" : x)));
}
  • 直接 push / splice で原本を変えないのがポイントです。

user01 Happy
お洋服は型紙を複製してお直し……。原本は大切に保管、ですわね🎀

6. 小さなアプリを仕立てましょう:ToDo with Sweetness

📝 解説(コード全体)

  • 追加、完了、フィルタの三拍子。コンポーネントを分けて役割を明確にします。
import { useState } from "react";

function TodoItem({ todo, onToggle, onRemove }) {
  return (
    <li>
      <label>
        <input
          type="checkbox"
          checked={todo.done}
          onChange={() => onToggle(todo.id)}
        />
        <span style={{ textDecoration: todo.done ? "line-through" : "none" }}>
          {todo.text}
        </span>
      </label>
      <button onClick={() => onRemove(todo.id)}>削除</button>
    </li>
  );
}

export default function SweetTodo() {
  const [text, setText] = useState("");
  const [filter, setFilter] = useState("all"); // all / active / done
  const [todos, setTodos] = useState([
    { id: 1, text: "ティーカップを磨く", done: false },
    { id: 2, text: "角砂糖を準備", done: true },
  ]);

  function addTodo(e) {
    e.preventDefault();
    if (!text.trim()) return;
    setTodos((ts) => [
      ...ts,
      { id: Date.now(), text: text.trim(), done: false },
    ]);
    setText("");
  }

  function toggle(id) {
    setTodos((ts) =>
      ts.map((t) => (t.id === id ? { ...t, done: !t.done } : t))
    );
  }

  function remove(id) {
    setTodos((ts) => ts.filter((t) => t.id !== id));
  }

  const visible = todos.filter((t) =>
    filter === "active" ? !t.done : filter === "done" ? t.done : true
  );

  return (
    <div>
      <h2>お茶会ToDo</h2>
      <form onSubmit={addTodo}>
        <input
          value={text}
          onChange={(e) => setText(e.target.value)}
          placeholder="やることを入力"
        />
        <button>追加</button>
      </form>

      <div style={{ marginTop: 8 }}>
        <button onClick={() => setFilter("all")}>すべて</button>
        <button onClick={() => setFilter("active")}>未完</button>
        <button onClick={() => setFilter("done")}>完了</button>
      </div>

      <ul>
        {visible.map((todo) => (
          <TodoItem
            key={todo.id}
            todo={todo}
            onToggle={toggle}
            onRemove={remove}
          />
        ))}
      </ul>
    </div>
  );
}
  • 役割の分離
    • SweetTodo:状態の。配列・フィルタ・入力欄を管理。
    • TodoItem:1件の表示と操作。
  • Stateの持ち場所は「必要とする最小の共通の親」に置くと整理が行き届きます(Stateの“持ち上げ”)。

user01 Serious
必要な方がまとめて管理、まるで家令の台帳のようですわ📜
user02 Calm
ええ、見通しがよくなります。
また、派生値(今回の `visible` のようにStateから計算できる値)は、
別のStateにせず、その場で計算するのがシンプルです。

7. よくある疑問に優雅に回答

📝 解説

  • Q. setStateしたのにすぐ反映されません?
    A. まとめて効率よく更新するため、即座に変数に反映されるとは限りません
    表示や次の計算に使うときは、関数型更新レンダー結果を信頼しましょう。

  • Q. 複数の入力欄を1つのオブジェクトで管理してよい?
    A. 小規模ならOK。ただし更新はスプレッドで不変に。

const [form, setForm] = useState({ name: "", tea: "" });
function onChange(e) {
  const { name, value } = e.target;
  setForm((f) => ({ ...f, [name]: value }));
}
  • Q. 子から親のStateを変えたい
    A. 親が“変更用の関数”をPropsで渡す(前回のイベント+今回のStateの合作)。

user01 Happy
なるほど……。お約束を守れば、サロンはいつも快適に回るのですわね✨

まとめ

  • State=コンポーネントの記憶useState で作り、setXxx で更新。
  • 連続更新は関数型更新が安心(setCount(c => c + 1))。
  • 入力欄はコントロールドにしてUIの真実をStateに。
  • オブジェクト/配列は不変更新(スプレッド、map / filter)で。
  • Stateの持ち場所は“必要な最小の共通の親”。派生値は都度計算。

user02 Happy
たいへんよくできました。
次回は、副作用(useEffect) を扱います。
「紅茶が冷めたらおかわりを淹れる」「画面の外の時計を合わせる」——
UIの外界と上品にお付き合いする作法です。
user01 Happy
まあ!サロンの空気まで整えるのでして?
わたくし、ますますReactが好きになってしまいましたわ☕✨
次回もどうぞよろしくお願いいたしますの。

蝋燭の灯りと窓の外——useEffectで広がる世界|第7回 React入門蝋燭の灯りと窓の外——useEffectで広がる世界|第7回 React入門