来たる韓国旅行に向けて、「自分専用の最強の旅のしおりアプリ」を作りました! 開発パートナーは生成AI。わがままな要望を投げまくって、PCでもスマホでも使えるWebアプリがあっという間に完成しました。

でも、感動したのは「アプリが動いたこと」だけではありません。 出力されたコードを読んでみると、「あれ?これ、Reactの勉強用教材として完璧じゃない…?」 と思うほど、行儀が良く、モダンな書き方がてんこ盛りだったんです。
今回は、AIが書いてくれたコードを元に、「なぜこの書き方が良いのか?」「どこがモダンなのか?」 を、備忘録を兼ねてAIに解説してもらいました😅。
あえて分割しない。「1ファイル完結」の美学
Reactの勉強を始めると、すぐに「コンポーネント(部品)ごとにファイルを分けましょう」と教わります。Header.jsx、Footer.jsx…みたいに。
でも、今回のアプリはあえて App.jsx という1つのファイルに全機能を詰め込みました。
なぜこれが「正解」だったのか?
- 行ったり来たりしなくていい: ロジックを追うために複数のファイルを開き直す必要がありません。
- データの流れが一目瞭然: 上から下へ読むだけで、「どこでデータが生まれ、どう加工され、どこで表示されるか」が全て見えます。
教科書通りの「モダンReact」テクニック
コードの中には、実務でも必須となる「Reactらしい書き方」が詰まっていました。
useState で「データの保存箱」を作る
アプリ内でユーザーが入力した予定や、フォームの状態はすべて useState で管理されています。
const [plans, setPlans] = useState([]); // 予定リスト
const [form, setForm] = useState({ ... }); // 入力フォームポイントは、「用途ごとに箱を分けている」こと。 全部ひとまとめにせず、役割ごとにState(状態)を作ることで、バグが起きにくく管理しやすくなっています。
データは「書き換え」ずに「置き換える」
これが一番の「モダンReact」ポイントです! 新しい予定を追加するとき、配列を直接いじる(pushする)のはReactではNGです。
setPlans([...plans, newPlan]);…plans(スプレッド構文)を使って、「今のリストをコピーして、新しい項目をくっつけた『新しいリスト』を作る」 という書き方をしています。
これを徹底することで、Reactは「あ、データが変わったな!」と正しく検知して、画面をサッと更新してくれます。これを「不変性(Immutability)を守る」と言うそうです。
useEffect で「データの永続化」
「ブラウザを閉じてもデータが消えない」ようにするために、useEffect と localStorage が使われています。
useEffect(() => {
localStorage.setItem("travel_plans", JSON.stringify(plans));
}, [plans]);ここで重要なのは、最後にある [plans] です。 これは「plans(予定リスト)の中身が変わった時だけ、保存処理を実行してね」という合図(依存配列)。 無駄な処理を走らせない、基本にして奥義のテクニックです。
フォーム管理の鉄則:「入力欄」をReactに支配させる
Webアプリで一番バグりやすいのが「入力フォーム」です。これを防ぐためのReactの鉄則が、「制御されたコンポーネント(Controlled Components)」 です。
<input
type="text"
value={form.title} // ① 表示する内容は State が決める
onChange={handleInputChange} // ② 入力したら State を更新する
/>入力欄を value と onChange でガチガチに管理することで、「見た目とデータが常に一致する」 という安全な状態を作っています。
条件付きレンダリング(Conditional Rendering)
React特有の「表示・非表示」の書き方です。
{isInputOpen && ( ... )} // 開いている時だけ表示
{editId ? "更新する" : "追加する"} // モードによって文字を変えるCSSで display: none するのではなく、プログラム的にDOM(要素)自体を出し入れする、Reactらしい制御方法です。
Webアプリの基本「CRUD」は map と filter で攻略せよ
Webアプリの基本機能である4つを全て実装しました。
Webアプリに必須の4機能(CRUD)も、Reactならスマートに書けます。特に重要なのが配列操作です。
- Read (表示): for文ではなく map を使う。
- Delete (削除): splice ではなく filter で「消したいもの以外」を残す。
- Update (更新): 配列を直接書き換えずに、 map で条件に合うものだけ差し替える。
昔ながらの for 文を書かずに、これらの「配列メソッド」を使うのがモダンな書き方です。 これを徹底することで、Reactの「不変性(Immutability)」のルールを守ることができ、バグを防げます。
計算結果は State にしない!最強の関数 reduce の活用
リスト操作で map や filter を使ったなら、計算(集計)もモダンに行きましょう。ここで登場するのが reduce です。
「合計金額」を useState で管理しがちですが、それはNG。 「データ(配列)があるなら、そこから計算すればいい」 というのが鉄則です。
// ❌ わざわざ合計金額の State を作ってしまう
const [totalCost, setTotalCost] = useState(0);
// データが増えるたびに、再計算して setTotalCost しなきゃ…(バグの温床)
// ⭕️ plans(データ)があるなら、そこから計算すればいいだけ!
const totalCost = plans.reduce((sum, plan) => sum + plan.cost, 0);ここで登場するのが、便利な関数 reduce です。 これは「配列の中身を全部なめて、1つの答え(合計)を出す」ための道具。
このように、reduce を使えば State を増やさずにスッキリ計算できます。 map, filter と合わせて、この 「配列操作の三種の神器」 を使いこなすのが、React上達への近道だと痛感しました。
この3つ(map, filter, reduce)がなぜセットで語られるべきかというと、「元のデータを汚さない(非破壊)」 という共通点があるからです。
「日付ごとに表示を分ける機能」も reduce で実装しています
// 配列を「日付ごとのグループ」に変換する魔法
const groupedPlans = plans.reduce((acc, plan) => {
const dateKey = plan.date || "undecided";
// ...グループ分けのロジック
return acc;
}, {});これは「合計」ではなく「配列を、使いやすい形(オブジェクト)に整形し直す」という、reduce の応用テクニックです。
【番外編】AIのここが細かすぎて凄い
ここ、実は上級テクニックです!
コードを見ていて「おや?」と思った箇所がありました。 useState の括弧の中が、単なる値ではなく 関数 になっているんです。
// 普通の書き方 毎回実行されちゃう
const [plans, setPlans] = useState(localStorage.getItem(...));
//AIが書いたコード(Lazy Initialization):
// 最初の1回だけ実行される(賢い!)
const [plans, setPlans] = useState(() => {
return localStorage.getItem(...);
});localStorage からデータを読み込むのは、コンピュータにとってはちょっと「重い」作業です。 普通に書くと、画面が更新されるたびに毎回読み込みに行こうとしてしまいます。
でも、() => { … } という関数で包んで渡すと、Reactは「わかった、最初の1回だけこれを実行して、あとは無視するね」 と判断してくれます。 これを 「Lazy Initialization(初期化の遅延)」 と呼ぶそうです。
