- DAY 29:オブジェクトState 前半 ― 「1つのまとまりとして状態を持つ」感覚を身につける
- オブジェクトをStateで管理する ― 「1人のユーザー」を丸ごと持つ
- Spread構文 ― 「コピー+一部上書き」のための基本ツール
- 一部更新 ― 入力フォームからオブジェクトStateを更新する
- ネストしたデータ ― 「オブジェクトの中にオブジェクト」がある場合
- 練習用テンプレート:DAY 29 前半版「オブジェクトStateプロフィールフォーム」
- DAY 29 前半のまとめ
- DAY 29:オブジェクトState 中盤 ― 「現実的なデータ構造」をReactで扱う感覚を育てる
- オブジェクトをStateで管理する ― 「1つの画面の状態」をまとめて持つ
- Spread構文 ― 「壊さずに変える」ための必須テクニック
- 一部更新 ― プロフィールと設定をそれぞれ安全に更新する
- ネストしたデータ ― 「画面全体の状態」を安全に扱う
- 練習用テンプレート:DAY 29 中盤版「プロフィール+設定画面」
- DAY 29 中盤のまとめ
- DAY 29:オブジェクトState(後半)― 「壊さずに育てる状態管理」を自分の標準にする
- オブジェクトStateを「画面の状態そのもの」として設計する
- Spread構文を「壊さずに育てるためのルール」として固定する
- 一部更新を「型」として覚える
- ネストしたデータを「怖がらずに扱う」ための視点
- 練習用テンプレート:DAY 29 後半版「タスク詳細+UI状態」
- DAY 29 後半のまとめ ― 「オブジェクトState+Spread+一部更新+ネスト」を自分の型にする
DAY 29:オブジェクトState 前半 ― 「1つのまとまりとして状態を持つ」感覚を身につける
DAY 29では、これまで配列Stateを中心に扱ってきた流れから一歩進んで、 「オブジェクトをStateで管理する」 ことにフォーカスして学習していきます。
現実のアプリでは、「1つのもの」に対して複数の情報が紐づくことがほとんどです。
- ユーザー情報:名前・メールアドレス・年齢
- プロフィール:自己紹介・アイコンURL・SNSリンク
- 設定:テーマカラー・通知ON/OFF・言語
こうした「複数のプロパティを持つ1つのまとまり」を扱うときに、 オブジェクトStateが登場します。
今回の学習項目は次の4つです。
- オブジェクトをStateで管理
- Spread構文
- 一部更新
- ネストしたデータ
これらを、ユーザープロフィールフォームを題材にして、 ステップバイステップでかみ砕いていきます。
オブジェクトをStateで管理する ― 「1人のユーザー」を丸ごと持つ
まずはシンプルなオブジェクトStateから
ユーザーのプロフィールを、次のようなオブジェクトで表現してみます。
const profile = {
name: "山田太郎",
email: "taro@example.com",
age: 25,
};
JavaScriptこれをReactのStateとして管理するときは、useState にそのまま渡します。
import { useState } from "react";
function ProfileForm() {
const [profile, setProfile] = useState({
name: "",
email: "",
age: "",
});
return (
<div>
<h2>プロフィール編集(オブジェクトState 前半)</h2>
{/* ここにフォームを作っていきます */}
</div>
);
}
export default ProfileForm;
JSXここでのポイントは、
profileという1つのStateの中に、name・email・ageという複数のプロパティが入っていること- 「1人のユーザー」を1つのオブジェクトとして扱っていること
です。
重要な感覚:
「1つのもの」に複数の情報が紐づくときは、 それを1つのオブジェクトとしてStateに持つ。
この感覚があると、State設計がぐっと自然になります。
Spread構文 ― 「コピー+一部上書き」のための基本ツール
なぜオブジェクトStateにSpread構文が必要なのか
オブジェクトStateの中の一部だけを更新したいとき、 次のように書きたくなることがあります。
// ❌ NG例:直接書き換え
profile.name = "新しい名前";
setProfile(profile);
JSXこれは、元の profile オブジェクトを直接書き換えています。
ReactのState更新では、 元のオブジェクトを直接書き換えるのは避けるべきです。
理由は、
- Reactは「前回のState」と「今回のState」の違いを、 オブジェクトの参照(別物かどうか)で判断することが多い
- 中身だけ書き換えると、「同じオブジェクトのまま中身だけ変わった」状態になり、 差分検知が難しくなることがある
からです。
Spread構文で「新しいオブジェクト」を作る
正しい形は、 Spread構文(...)を使って「コピー+一部上書き」した新しいオブジェクトを作ることです。
const updatedProfile = {
...profile,
name: "新しい名前",
};
setProfile(updatedProfile);
JSXここで起きていることは、
{ ...profile }で、profileの全プロパティをコピーした新しいオブジェクトを作るname: "新しい名前"で、その新しいオブジェクトのnameだけを上書きする
という流れです。
この updatedProfile は、元の profile とは別のオブジェクトです。 そのため、Reactは「Stateが変わった」と認識しやすくなります。
ここが、オブジェクトStateにおけるSpread構文の本質です。
一部更新 ― 入力フォームからオブジェクトStateを更新する
名前入力欄を作る
実際にフォームを作りながら、一部更新を体に馴染ませていきます。
function ProfileForm() {
const [profile, setProfile] = useState({
name: "",
email: "",
age: "",
});
const handleChangeName = (e) => {
const newName = e.target.value;
setProfile((prev) => ({
...prev,
name: newName,
}));
};
return (
<div>
<h2>プロフィール編集(オブジェクトState 前半)</h2>
<div>
<label>
名前:
<input
type="text"
value={profile.name}
onChange={handleChangeName}
/>
</label>
</div>
</div>
);
}
JSXここで起きていることを分解してみます。
value={profile.name}→ Stateのprofile.nameを入力欄に表示しています。 入力欄はReactに「支配」されている状態です。onChange={handleChangeName}→ ユーザーが入力するたびにイベントが発生し、handleChangeNameが呼ばれます。setProfile((prev) => ({ ...prev, name: newName }))→ 以前のprofileをprevとして受け取り、 Spread構文でコピーしつつnameだけを新しい値に上書きした 新しいオブジェクトをStateとして保存しています。
重要ポイント:
オブジェクトStateの一部更新は、
setProfile((prev) => ({ ...prev, 変更したいプロパティ: 新しい値 }))という形が基本パターンになる。
この形を、名前・メール・年齢など、 複数の入力欄で繰り返し使っていきます。
ネストしたデータ ― 「オブジェクトの中にオブジェクト」がある場合
ネスト構造の例を考える
現実のアプリでは、 オブジェクトの中にさらにオブジェクトが入る「ネストしたデータ」がよく登場します。
例えば、次のようなプロフィールデータです。
const profile = {
name: "山田太郎",
email: "taro@example.com",
age: 25,
address: {
city: "東京",
zip: "123-4567",
},
};
JavaScriptここでは、
profileが「ユーザー全体」addressが「住所情報」address.cityやaddress.zipが具体的な項目
という構造になっています。
ネストしたデータの一部更新
address.city だけを更新したいとき、 次のように書きたくなるかもしれません。
// ❌ NG例:直接書き換え
profile.address.city = "大阪";
setProfile(profile);
JSXこれは、profile.address の中身を直接書き換えています。 やはり、ReactのState更新としては避けるべきです。
正しい形は、 外側と内側の両方でSpread構文を使うことです。
setProfile((prev) => ({
...prev,
address: {
...prev.address,
city: "大阪",
},
}));
JSXここで起きていることは、
...prevで、元のprofileをコピーした新しいオブジェクトを作るaddress: { ...prev.address, city: "大阪" }で、- 元の
addressをコピーした新しいオブジェクトを作り - その中の
cityだけを上書きする
- 元の
という二段構えです。
重要ポイント:
ネストしたデータの一部更新は、 「外側のオブジェクト」と「内側のオブジェクト」の両方を Spread構文でコピー+上書きする。
この形は少し長くなりますが、 「元のオブジェクトを壊さず、新しいオブジェクトを作る」という Reactの不変性のルールを守るために重要です。
練習用テンプレート:DAY 29 前半版「オブジェクトStateプロフィールフォーム」
最後に、DAY 29前半の学習項目をすべて含んだ シンプルなプロフィールフォームのテンプレートを示します。
import { useState } from "react";
function ProfileForm() {
const [profile, setProfile] = useState({
name: "",
email: "",
age: "",
address: {
city: "",
zip: "",
},
});
const handleChangeName = (e) => {
const newName = e.target.value;
setProfile((prev) => ({
...prev,
name: newName,
}));
};
const handleChangeEmail = (e) => {
const newEmail = e.target.value;
setProfile((prev) => ({
...prev,
email: newEmail,
}));
};
const handleChangeAge = (e) => {
const newAge = e.target.value;
setProfile((prev) => ({
...prev,
age: newAge,
}));
};
const handleChangeCity = (e) => {
const newCity = e.target.value;
setProfile((prev) => ({
...prev,
address: {
...prev.address,
city: newCity,
},
}));
};
const handleChangeZip = (e) => {
const newZip = e.target.value;
setProfile((prev) => ({
...prev,
address: {
...prev.address,
zip: newZip,
},
}));
};
return (
<div>
<h2>プロフィール編集(オブジェクトState 前半)</h2>
<div>
<label>
名前:
<input
type="text"
value={profile.name}
onChange={handleChangeName}
/>
</label>
</div>
<div>
<label>
メール:
<input
type="email"
value={profile.email}
onChange={handleChangeEmail}
/>
</label>
</div>
<div>
<label>
年齢:
<input
type="number"
value={profile.age}
onChange={handleChangeAge}
/>
</label>
</div>
<div>
<label>
住所(市区町村):
<input
type="text"
value={profile.address.city}
onChange={handleChangeCity}
/>
</label>
</div>
<div>
<label>
郵便番号:
<input
type="text"
value={profile.address.zip}
onChange={handleChangeZip}
/>
</label>
</div>
<hr />
<div>
<h3>現在のプロフィール</h3>
<p>名前:{profile.name}</p>
<p>メール:{profile.email}</p>
<p>年齢:{profile.age}</p>
<p>住所:{profile.address.city}</p>
<p>郵便番号:{profile.address.zip}</p>
</div>
</div>
);
}
export default ProfileForm;
JSXDAY 29 前半のまとめ
DAY 29前半で身につけてほしいのは、次の感覚です。
- オブジェクトをStateで管理することで、「1つのもの」に複数の情報が紐づく現実的なデータを自然に扱えること
- Spread構文は、「元のオブジェクトを壊さず、新しいオブジェクトを作る」ための基本ツールであり、オブジェクトStateの一部更新に必須であること
- 一部更新は、
setProfile((prev) => ({ ...prev, プロパティ: 新しい値 }))という形が基本パターンになること - ネストしたデータの一部更新は、「外側」と「内側」の両方でSpread構文を使ってコピー+上書きする二段構えになること
このオブジェクトStateの扱い方が身につくと、
- ユーザー情報
- 設定画面
- プロフィール編集
- 詳細画面の編集
など、現実的なUIを支える「まとまりのある状態」を、 落ち着いて・安全に・拡張しやすい形で扱えるようになります。
DAY 29:オブジェクトState 中盤 ― 「現実的なデータ構造」をReactで扱う感覚を育てる
DAY 29の中盤では、前半で触れた オブジェクトState を、 より「現実のアプリに近い形」で扱えるようになることを目指します。
学習項目は同じく、
- オブジェクトをStateで管理
- Spread構文
- 一部更新
- ネストしたデータ
ですが、ここでは「設計目線」と「安全性」を意識しながら、 プロフィール+設定のような、少し複雑なオブジェクトを題材にして深掘りしていきます。
オブジェクトをStateで管理する ― 「1つの画面の状態」をまとめて持つ
プロフィール+設定を1つのStateにまとめる
中盤では、単なるプロフィールだけでなく、 「プロフィール+設定」を1つのオブジェクトStateで扱ってみます。
例えば、次のような構造です。
const initialState = {
profile: {
name: "",
email: "",
},
settings: {
theme: "light",
notifications: true,
},
};
JavaScriptこれをReactのStateとして持つと、こうなります。
import { useState } from "react";
function UserPage() {
const [userState, setUserState] = useState({
profile: {
name: "",
email: "",
},
settings: {
theme: "light",
notifications: true,
},
});
return (
<div>
<h2>ユーザー画面(オブジェクトState 中盤)</h2>
{/* ここにフォームや設定UIを作っていきます */}
</div>
);
}
export default UserPage;
JSXここでのポイントは、
userStateという1つのStateの中に、profileとsettingsという2つの「まとまり」が入っていること- 「この画面に必要な状態」を1つのオブジェクトとしてまとめていること
です。
大事な感覚:
「この画面で扱う状態」を、1つのオブジェクトにまとめて持つ。 その中に、さらに小さなまとまり(profile・settingsなど)を入れていく。
これが、現実的なUIのState設計の入り口になります。
Spread構文 ― 「壊さずに変える」ための必須テクニック
外側と内側、どちらも壊さない
userState の中の一部だけを更新したいとき、 例えば「名前だけ変えたい」とします。
NGな書き方はこうです。
// ❌ NG例:直接書き換え
userState.profile.name = "新しい名前";
setUserState(userState);
JSXこれは、
userState.profileの中身を直接書き換えているuserState自体は「同じオブジェクトのまま」なので、 Reactが変化を検知しにくい
という問題があります。
正しい形は、 外側と内側の両方でSpread構文を使うことです。
setUserState((prev) => ({
...prev,
profile: {
...prev.profile,
name: "新しい名前",
},
}));
JSXここで起きていることは、
...prevで、元のuserStateをコピーした新しいオブジェクトを作るprofile: { ...prev.profile, name: "新しい名前" }で、- 元の
profileをコピーした新しいオブジェクトを作り - その中の
nameだけを上書きする
- 元の
という二段構えです。
重要ポイント:
ネストしたオブジェクトを更新するときは、 「外側のオブジェクト」と「内側のオブジェクト」の両方を Spread構文でコピー+上書きする。
これが「壊さずに変える」ための基本ルールになります。
一部更新 ― プロフィールと設定をそれぞれ安全に更新する
プロフィールの一部更新(名前・メール)
実際にフォームを作りながら、一部更新を体に馴染ませていきます。
function UserPage() {
const [userState, setUserState] = useState({
profile: {
name: "",
email: "",
},
settings: {
theme: "light",
notifications: true,
},
});
const handleChangeName = (e) => {
const newName = e.target.value;
setUserState((prev) => ({
...prev,
profile: {
...prev.profile,
name: newName,
},
}));
};
const handleChangeEmail = (e) => {
const newEmail = e.target.value;
setUserState((prev) => ({
...prev,
profile: {
...prev.profile,
email: newEmail,
},
}));
};
return (
<div>
<h2>ユーザー画面(オブジェクトState 中盤)</h2>
<div>
<label>
名前:
<input
type="text"
value={userState.profile.name}
onChange={handleChangeName}
/>
</label>
</div>
<div>
<label>
メール:
<input
type="email"
value={userState.profile.email}
onChange={handleChangeEmail}
/>
</label>
</div>
</div>
);
}
JSXここでの流れは、
- 入力欄の
valueにuserState.profile.name/emailを渡す onChangeで新しい値を受け取り、setUserState+Spread構文で「profileだけ一部更新した新しいオブジェクト」を作る- その新しいオブジェクトをStateとして保存し、Reactが再レンダリングする
というものです。
設定の一部更新(テーマ・通知)
設定側も同じパターンで更新できます。
const handleToggleNotifications = () => {
setUserState((prev) => ({
...prev,
settings: {
...prev.settings,
notifications: !prev.settings.notifications,
},
}));
};
const handleChangeTheme = (e) => {
const newTheme = e.target.value; // "light" or "dark"
setUserState((prev) => ({
...prev,
settings: {
...prev.settings,
theme: newTheme,
},
}));
};
JSX表示側はこうなります。
<div>
<label>
テーマ:
<select
value={userState.settings.theme}
onChange={handleChangeTheme}
>
<option value="light">ライト</option>
<option value="dark">ダーク</option>
</select>
</label>
</div>
<div>
<label>
通知:
<input
type="checkbox"
checked={userState.settings.notifications}
onChange={handleToggleNotifications}
/>
</label>
</div>
JSXここで意識したいこと:
- プロフィールも設定も、 「外側のState(userState)」の中の一部として扱っていること
- どちらも、
setUserState((prev) => ({ ...prev, 内側: { ...prev.内側, プロパティ: 新しい値 } }))という同じ型で更新していること
この「同じ型で書ける」感覚が、中盤で育てたいところです。
ネストしたデータ ― 「画面全体の状態」を安全に扱う
ネストが深くなっても考え方は同じ
ネストしたデータは、 深くなればなるほどSpread構文の入れ子が増えます。
例えば、さらに「通知の詳細設定」が入った場合を考えてみます。
const userState = {
profile: { name: "", email: "" },
settings: {
theme: "light",
notifications: true,
notificationDetail: {
email: true,
push: false,
},
},
};
JavaScriptnotificationDetail.email だけを更新したいときは、こうなります。
setUserState((prev) => ({
...prev,
settings: {
...prev.settings,
notificationDetail: {
...prev.settings.notificationDetail,
email: !prev.settings.notificationDetail.email,
},
},
}));
JSX長くはなりますが、やっていることは前と同じです。
- 外側(userState)をSpreadでコピー
- その中の
settingsをSpreadでコピー - さらにその中の
notificationDetailをSpreadでコピー - 最後に
emailだけを上書き
という流れです。
重要ポイント:
ネストが深くなっても、 「外側から順番にSpreadでコピー+上書きする」 というルールは変わらない。
このルールを守ることで、 「どこかで元のオブジェクトを壊してしまう」事故を防げます。
練習用テンプレート:DAY 29 中盤版「プロフィール+設定画面」
中盤の内容をすべて含んだテンプレートを示します。
import { useState } from "react";
function UserPage() {
const [userState, setUserState] = useState({
profile: {
name: "",
email: "",
},
settings: {
theme: "light",
notifications: true,
},
});
const handleChangeName = (e) => {
const newName = e.target.value;
setUserState((prev) => ({
...prev,
profile: {
...prev.profile,
name: newName,
},
}));
};
const handleChangeEmail = (e) => {
const newEmail = e.target.value;
setUserState((prev) => ({
...prev,
profile: {
...prev.profile,
email: newEmail,
},
}));
};
const handleChangeTheme = (e) => {
const newTheme = e.target.value;
setUserState((prev) => ({
...prev,
settings: {
...prev.settings,
theme: newTheme,
},
}));
};
const handleToggleNotifications = () => {
setUserState((prev) => ({
...prev,
settings: {
...prev.settings,
notifications: !prev.settings.notifications,
},
}));
};
return (
<div>
<h2>ユーザー画面(オブジェクトState 中盤)</h2>
<div>
<label>
名前:
<input
type="text"
value={userState.profile.name}
onChange={handleChangeName}
/>
</label>
</div>
<div>
<label>
メール:
<input
type="email"
value={userState.profile.email}
onChange={handleChangeEmail}
/>
</label>
</div>
<div>
<label>
テーマ:
<select
value={userState.settings.theme}
onChange={handleChangeTheme}
>
<option value="light">ライト</option>
<option value="dark">ダーク</option>
</select>
</label>
</div>
<div>
<label>
通知:
<input
type="checkbox"
checked={userState.settings.notifications}
onChange={handleToggleNotifications}
/>
</label>
</div>
<hr />
<div>
<h3>現在の状態</h3>
<p>名前:{userState.profile.name}</p>
<p>メール:{userState.profile.email}</p>
<p>テーマ:{userState.settings.theme}</p>
<p>通知:{userState.settings.notifications ? "ON" : "OFF"}</p>
</div>
</div>
);
}
export default UserPage;
JSXDAY 29 中盤のまとめ
DAY 29中盤で、自分の中に定着させてほしいのは次の4つです。
- オブジェクトをStateで管理することで、「この画面に必要な状態」を1つのまとまりとして扱えること
- Spread構文は、「外側」と「内側」の両方で使うことで、ネストしたオブジェクトを壊さずに更新できること
- 一部更新は、
setState((prev) => ({ ...prev, 内側: { ...prev.内側, プロパティ: 新しい値 } }))という型で統一できること - ネストしたデータは、深くなっても「外側から順番にコピー+上書きする」というルールを守れば、安全に扱えること
このオブジェクトStateの扱い方が身につくと、
- プロフィール画面
- 設定画面
- ダッシュボード
- 複数タブを持つ画面
など、「画面全体の状態」をReactでしっかり管理できるようになっていきます。
DAY 29:オブジェクトState(後半)― 「壊さずに育てる状態管理」を自分の標準にする
DAY 29の後半では、 これまで学んできた オブジェクトState+Spread構文+一部更新+ネストしたデータ を、 「どんな画面でも通用する自分の型」に仕上げていきます。
ここまでで、
- オブジェクトをStateで持つ
- Spread構文で一部更新する
- ネストしたデータを外側・内側両方コピーして更新する
という基本はすでに触れています。
後半では、それを 「実際のアプリで使えるレベル」 にまで引き上げるイメージで読んでください。
オブジェクトStateを「画面の状態そのもの」として設計する
1つの画面に必要な情報を、1つのオブジェクトにまとめる
後半では、オブジェクトStateを単なる「便利な入れ物」ではなく、 「画面の状態そのもの」 として意識してみます。
例えば、次のような「タスク詳細画面」を考えます。
- タスクのタイトル・説明・期限
- タスクの完了状態
- タスクに紐づく担当者情報
- 画面上のUI状態(編集モードかどうか、保存中かどうか)
これを1つのStateにまとめると、こうなります。
const initialTaskState = {
task: {
id: 1,
title: "",
description: "",
dueDate: "",
done: false,
assignee: {
name: "",
email: "",
},
},
ui: {
isEditing: false,
isSaving: false,
},
};
JavaScriptReactコンポーネントではこう書きます。
import { useState } from "react";
function TaskDetailPage() {
const [state, setState] = useState({
task: {
id: 1,
title: "",
description: "",
dueDate: "",
done: false,
assignee: {
name: "",
email: "",
},
},
ui: {
isEditing: false,
isSaving: false,
},
});
return (
<div>
<h2>タスク詳細(オブジェクトState 後半)</h2>
{/* ここにフォームやボタンを作っていきます */}
</div>
);
}
export default TaskDetailPage;
JSXここで意識してほしいのは、
stateという1つのオブジェクトが、 「この画面に必要なすべての状態」を持っていることtaskとuiという2つの大きなまとまりがあり、 それぞれがさらにネストしたデータを持っていること
です。
重要な感覚:
オブジェクトStateは、「画面の状態」を丸ごと表現するための器。 その中に、意味のあるまとまりごとにネストしていく。
この設計目線があると、Stateが「散らばる」のではなく、 1つの軸で整理されていきます。
Spread構文を「壊さずに育てるためのルール」として固定する
直接書き換えは絶対にしない、を後半で強く固める
ネストしたオブジェクトを扱うとき、 ついこう書きたくなることがあります。
// ❌ NG例:直接書き換え
state.task.title = "新しいタイトル";
setState(state);
JSXこれは、
state.taskの中身を直接書き換えているstate自体は「同じオブジェクトのまま」なので、 Reactが変化を検知しにくい
という問題があります。
後半では、ここを 「絶対にやらない」 と自分の中で決めてしまうのがおすすめです。
代わりに、必ず Spread構文で「コピー+上書き」します。
setState((prev) => ({
...prev,
task: {
...prev.task,
title: "新しいタイトル",
},
}));
JSXさらに、担当者のメールアドレスだけ変えたい場合はこうです。
setState((prev) => ({
...prev,
task: {
...prev.task,
assignee: {
...prev.task.assignee,
email: "new-email@example.com",
},
},
}));
JSXここで固めたいルール:
ネストしたオブジェクトを更新するときは、 外側から順番に Spread でコピー+上書きする。 直接書き換えはしない。
このルールを守ることで、
- 「どこかで元のオブジェクトを壊してしまう」
- 「画面が更新されない」
といった事故を避けられます。
一部更新を「型」として覚える
タスクのタイトル・説明・期限を更新する
実際にフォームを作りながら、 一部更新の「型」を体に染み込ませていきます。
function TaskDetailPage() {
const [state, setState] = useState({
task: {
id: 1,
title: "",
description: "",
dueDate: "",
done: false,
assignee: {
name: "",
email: "",
},
},
ui: {
isEditing: false,
isSaving: false,
},
});
const handleChangeTitle = (e) => {
const newTitle = e.target.value;
setState((prev) => ({
...prev,
task: {
...prev.task,
title: newTitle,
},
}));
};
const handleChangeDescription = (e) => {
const newDescription = e.target.value;
setState((prev) => ({
...prev,
task: {
...prev.task,
description: newDescription,
},
}));
};
const handleChangeDueDate = (e) => {
const newDueDate = e.target.value;
setState((prev) => ({
...prev,
task: {
...prev.task,
dueDate: newDueDate,
},
}));
};
return (
<div>
<h2>タスク詳細(オブジェクトState 後半)</h2>
<div>
<label>
タイトル:
<input
type="text"
value={state.task.title}
onChange={handleChangeTitle}
/>
</label>
</div>
<div>
<label>
説明:
<textarea
value={state.task.description}
onChange={handleChangeDescription}
/>
</label>
</div>
<div>
<label>
期限:
<input
type="date"
value={state.task.dueDate}
onChange={handleChangeDueDate}
/>
</label>
</div>
</div>
);
}
JSXここでの共通パターンは、
setState((prev) => ({
...prev,
task: {
...prev.task,
プロパティ: 新しい値,
},
}));
JSXという形です。
UI状態(編集モード・保存中)を更新する
UI側の状態も同じ型で更新できます。
const toggleEditing = () => {
setState((prev) => ({
...prev,
ui: {
...prev.ui,
isEditing: !prev.ui.isEditing,
},
}));
};
const setSaving = (isSaving) => {
setState((prev) => ({
...prev,
ui: {
...prev.ui,
isSaving,
},
}));
};
JSX表示側はこうなります。
<button onClick={toggleEditing}>
{state.ui.isEditing ? "編集終了" : "編集する"}
</button>
{state.ui.isSaving && <p>保存中です...</p>}
JSXここで定着させたいこと:
「どの部分を変えたいか」に応じて、 その部分までSpreadで降りていき、 最後にプロパティを上書きする。 それがオブジェクトStateの一部更新の型。
ネストしたデータを「怖がらずに扱う」ための視点
ネストは「階層構造」として落ち着いて読む
ネストしたデータは、 見た目のコードが長くなるので、最初は少し怖く感じるかもしれません。
例えば、担当者の名前だけ変えたい場合はこうです。
const handleChangeAssigneeName = (e) => {
const newName = e.target.value;
setState((prev) => ({
...prev,
task: {
...prev.task,
assignee: {
...prev.task.assignee,
name: newName,
},
},
}));
};
JSXここを「階層構造」として読むと、落ち着いて理解できます。
- 最上位:
state - その中の:
task - さらにその中の:
assignee - 最後に:
nameを上書き
という階段を降りているイメージです。
ポイント:
ネストしたデータは、「どの階層まで降りていくか」を意識して読む。 Spread構文は、その階層ごとに「壊さずコピーする」ための道具。
この視点があると、 ネストが深くなっても「ただ階段が増えただけ」と感じられるようになります。
練習用テンプレート:DAY 29 後半版「タスク詳細+UI状態」
DAY 29後半の内容をすべて含んだテンプレートをまとめます。
import { useState } from "react";
function TaskDetailPage() {
const [state, setState] = useState({
task: {
id: 1,
title: "",
description: "",
dueDate: "",
done: false,
assignee: {
name: "",
email: "",
},
},
ui: {
isEditing: false,
isSaving: false,
},
});
const handleChangeTitle = (e) => {
const newTitle = e.target.value;
setState((prev) => ({
...prev,
task: {
...prev.task,
title: newTitle,
},
}));
};
const handleChangeDescription = (e) => {
const newDescription = e.target.value;
setState((prev) => ({
...prev,
task: {
...prev.task,
description: newDescription,
},
}));
};
const handleChangeDueDate = (e) => {
const newDueDate = e.target.value;
setState((prev) => ({
...prev,
task: {
...prev.task,
dueDate: newDueDate,
},
}));
};
const handleChangeAssigneeName = (e) => {
const newName = e.target.value;
setState((prev) => ({
...prev,
task: {
...prev.task,
assignee: {
...prev.task.assignee,
name: newName,
},
},
}));
};
const handleChangeAssigneeEmail = (e) => {
const newEmail = e.target.value;
setState((prev) => ({
...prev,
task: {
...prev.task,
assignee: {
...prev.task.assignee,
email: newEmail,
},
},
}));
};
const toggleEditing = () => {
setState((prev) => ({
...prev,
ui: {
...prev.ui,
isEditing: !prev.ui.isEditing,
},
}));
};
const toggleDone = () => {
setState((prev) => ({
...prev,
task: {
...prev.task,
done: !prev.task.done,
},
}));
};
return (
<div>
<h2>タスク詳細(オブジェクトState 後半)</h2>
<button onClick={toggleEditing}>
{state.ui.isEditing ? "編集終了" : "編集する"}
</button>
<div>
<label>
タイトル:
<input
type="text"
value={state.task.title}
onChange={handleChangeTitle}
disabled={!state.ui.isEditing}
/>
</label>
</div>
<div>
<label>
説明:
<textarea
value={state.task.description}
onChange={handleChangeDescription}
disabled={!state.ui.isEditing}
/>
</label>
</div>
<div>
<label>
期限:
<input
type="date"
value={state.task.dueDate}
onChange={handleChangeDueDate}
disabled={!state.ui.isEditing}
/>
</label>
</div>
<div>
<label>
担当者名:
<input
type="text"
value={state.task.assignee.name}
onChange={handleChangeAssigneeName}
disabled={!state.ui.isEditing}
/>
</label>
</div>
<div>
<label>
担当者メール:
<input
type="email"
value={state.task.assignee.email}
onChange={handleChangeAssigneeEmail}
disabled={!state.ui.isEditing}
/>
</label>
</div>
<div>
<label>
完了:
<input
type="checkbox"
checked={state.task.done}
onChange={toggleDone}
/>
</label>
</div>
<hr />
<div>
<h3>現在の状態</h3>
<p>タイトル:{state.task.title}</p>
<p>説明:{state.task.description}</p>
<p>期限:{state.task.dueDate}</p>
<p>担当者:{state.task.assignee.name}</p>
<p>担当者メール:{state.task.assignee.email}</p>
<p>完了:{state.task.done ? "はい" : "いいえ"}</p>
<p>編集モード:{state.ui.isEditing ? "ON" : "OFF"}</p>
</div>
</div>
);
}
export default TaskDetailPage;
JSXDAY 29 後半のまとめ ― 「オブジェクトState+Spread+一部更新+ネスト」を自分の型にする
DAY 29後半で、自分の中に固定してほしいのは次の4つです。
- オブジェクトをStateで管理することで、「画面の状態」を1つのオブジェクトとして表現できること
- Spread構文は、「壊さずに育てる」ためのルールであり、外側から順番にコピー+上書きすることでネストしたデータも安全に更新できること
- 一部更新は、
setState((prev) => ({ ...prev, 内側: { ...prev.内側, プロパティ: 新しい値 } }))という型で統一できること - ネストしたデータは、「階層構造」として落ち着いて読み、どの階層まで降りていくかを意識しながらSpreadを重ねていけば怖くないこと
この型が自分の中で固まると、
- タスク詳細画面
- プロフィール+設定画面
- 複雑なフォーム
- ダッシュボードの状態管理
など、現実的なアプリの「状態の塊」を、 Reactで落ち着いて、安全に、拡張しやすく扱えるようになっていきます。
