
砂糖は一粒ずつ——Stateで育てるインタラクション|第6回 React入門
著者: 管理者 / 2025-08-21 (更新: 2025-08-21)
導入
紅茶にお砂糖をひとつ、またひとつ——入れるたびに甘さが変わるように、アプリも「いま」の状態によって表情が変わります。
前回、ボタンを押す“出来事(イベント)”を学びました。今回は、その出来事に意味を与える「State(状態)」を、お嬢様と一緒に育ててまいりましょう☕🍬
前回の記事
ボタンが踊り出す!イベント処理|第5回 React入門

先生、前回のボタンは光って見えましたわ!
でも……押した回数を覚えていてほしい時、どういたしますの?
でも……押した回数を覚えていてほしい時、どういたしますの?

良い着眼点です。
State を使うと「押した回数」のような“変化する情報”をコンポーネントに持たせられます。
イベントは火花、Stateはその火が灯るロウソクの炎だと思ってください。
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が再描画、という流れです。

まあ!砂糖を一粒ずつ増やすたびに、甘さ(表示)が変わりますのね✨

そのとおり。StateはUIのソース・オブ・トゥルース(真実の源)。
表示は常にStateから計算されます。
表示は常にStateから計算されます。
2. 「直接書き換え」はご法度:不変性の礼儀作法
📝 解説
-
Stateは直接代入で書き換えてはいけません。
例)count = count + 1
は❌。必ずsetCount
を使います。 -
なぜ?——Reactは「Stateが変わった」という合図でUIを更新します。
直接代入だと合図にならず、表示が変わらない恐れがあります。

礼儀を守らないと、執事(React)が気づいてくださらないのですわね……。
3. 連続更新の落とし穴と“関数型更新”
📝 解説
- 次のように書くと、意図通りに2回増えないことがあります。
setCount(count + 1);
setCount(count + 1); // 同じ"古い"countを参照してしまう場合がある
- 解決策:関数型更新 で「前回の値」を受け取り、そこから計算します。
setCount((c) => c + 1);
setCount((c) => c + 1); // これで+2が確実に反映される
- これなら、Reactの賢いまとめ(バッチ処理)があっても安全です。

まあ!前回の値を渡していただけるなんて、優雅なエスコートですこと💃
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が主役です。

入力も衣装合わせのように、寸法(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
で原本を変えないのがポイントです。

お洋服は型紙を複製してお直し……。原本は大切に保管、ですわね🎀
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の“持ち上げ”)。

必要な方がまとめて管理、まるで家令の台帳のようですわ📜

ええ、見通しがよくなります。
また、派生値(今回の `visible` のようにStateから計算できる値)は、
別のStateにせず、その場で計算するのがシンプルです。
また、派生値(今回の `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の合作)。

なるほど……。お約束を守れば、サロンはいつも快適に回るのですわね✨
まとめ
- State=コンポーネントの記憶。
useState
で作り、setXxx
で更新。 - 連続更新は関数型更新が安心(
setCount(c => c + 1)
)。 - 入力欄はコントロールドにしてUIの真実をStateに。
- オブジェクト/配列は不変更新(スプレッド、
map
/filter
)で。 - Stateの持ち場所は“必要な最小の共通の親”。派生値は都度計算。

たいへんよくできました。
次回は、副作用(useEffect) を扱います。
「紅茶が冷めたらおかわりを淹れる」「画面の外の時計を合わせる」——
UIの外界と上品にお付き合いする作法です。
次回は、副作用(useEffect) を扱います。
「紅茶が冷めたらおかわりを淹れる」「画面の外の時計を合わせる」——
UIの外界と上品にお付き合いする作法です。

まあ!サロンの空気まで整えるのでして?
わたくし、ますますReactが好きになってしまいましたわ☕✨
次回もどうぞよろしくお願いいたしますの。
わたくし、ますますReactが好きになってしまいましたわ☕✨
次回もどうぞよろしくお願いいたしますの。