経験浅いエンジニアが AI に React 書かせてると、進化に取り残される説
📝 はじめに
こんにちは、シュートです。前回は「就活中のジュニアエンジニアが面接で痛感した『力不足』の話」を書きました。読んでくださった方、ありがとうございました。
その記事の最後で、「AIに頼って速さだけで進まず、一つひとつちゃんと理解してから次に進む」と書きました。今回はその続編、というか関連する別の気づきです。
アルバイトと個人開発で、React と TypeScript を書いています。エンジニア歴は1年ほどで、正直、React 自体が進化していることを「リアルタイムで体感する」ような経験はあまりありません。勉強し始めた頃と今、自分の中ではそんなに違いを感じていませんでした。
でも、最近ふと気づいたんです。
AI に React のコードを書かせると、結構な確率で「古い書き方」が返ってくる。
フォームの状態が手動の useState だらけだったり、ローディング管理が isLoading の手動切り替えだったり。それが自分にとっての"普通"になっていたんですが、よく調べると React 19 ではもっとシンプルに書ける方法が用意されていました。
経験の浅い自分は、何が古くて何が新しいか判断する基準を持っていないので、AI が書いたコードをそのまま「これが React の書き方」として吸収してしまっていた、ということです。
この記事では、その気づきを お問い合わせフォーム という具体例で確かめてみます。
ちなみに今回 AI に書かせたのは、現時点で最新の ChatGPT(GPT-5.5) です。「古いモデルだからでしょ?」というツッコミは入らないバージョンです。
同じように「AI に書かせて動いたから、それを React の書き方だと思ってる」ジュニアエンジニアに、何かしらの参考になれば嬉しいです。
💻 試してみた ── お問い合わせフォーム
AI に投げたプロンプト
使ったのは ChatGPT(モデル: GPT-5.5)です。
React と TypeScript で、シンプルな問い合わせフォーム(名前、メールアドレス、メッセージの3項目)を作ってください。送信時に API に POST する処理と、送信中のローディング表示も含めてください。
返ってきたコード
import { useState } from "react";
type ContactFormData = {
name: string;
email: string;
message: string;
};
export default function ContactForm() {
const [formData, setFormData] = useState<ContactFormData>({
name: "",
email: "",
message: "",
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [successMessage, setSuccessMessage] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setSuccessMessage("");
setErrorMessage("");
setIsSubmitting(true);
try {
const response = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData),
});
if (!response.ok) throw new Error("送信に失敗しました");
setSuccessMessage("お問い合わせを送信しました。");
setFormData({ name: "", email: "", message: "" });
} catch (error) {
setErrorMessage("送信中にエラーが発生しました。もう一度お試しください。");
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">名前</label>
<input
id="name"
name="name"
type="text"
value={formData.name}
onChange={handleChange}
required
disabled={isSubmitting}
/>
</div>
<div>
<label htmlFor="email">メールアドレス</label>
<input
id="email"
name="email"
type="email"
value={formData.email}
onChange={handleChange}
required
disabled={isSubmitting}
/>
</div>
<div>
<label htmlFor="message">メッセージ</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
required
disabled={isSubmitting}
rows={5}
/>
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "送信中..." : "送信する"}
</button>
{successMessage && <p style={{ color: "green" }}>{successMessage}</p>}
{errorMessage && <p style={{ color: "red" }}>{errorMessage}</p>}
</form>
);
}
(スタイル定義はオリジナルにはありますが、本質ではないので省略しています)
念のため、これは 2026年時点で最新の GPT-5.5 が返してきたコードです。古いモデルではありません。
これも、自分の中では普通の書き方でした
正直に言うと、自分が書くフォームもだいたいこれと同じ構造です。
-
useStateを4つ作る(formData、isSubmitting、successMessage、errorMessage) -
onChangeで都度入力値を state に保存する -
handleSubmitでsetIsSubmitting(true)→ fetch →setIsSubmitting(false)
「フォームの状態は React の state で管理する」── 自分の中では絶対のルールでした。
でもこれ、React 19 では 半分以上が要らない ことを知ってしまったんです。
React 19 だとこう書ける
import { useActionState } from "react";
type FormState = {
success: boolean;
message: string;
};
async function submitContact(
prevState: FormState,
formData: FormData
): Promise<FormState> {
const data = {
name: formData.get("name") as string,
email: formData.get("email") as string,
message: formData.get("message") as string,
};
try {
const response = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error("送信に失敗しました");
return { success: true, message: "お問い合わせを送信しました。" };
} catch (error) {
return {
success: false,
message: "送信中にエラーが発生しました。もう一度お試しください。",
};
}
}
export default function ContactForm() {
const [state, formAction, isPending] = useActionState(submitContact, {
success: false,
message: "",
});
return (
<form action={formAction}>
<div>
<label htmlFor="name">名前</label>
<input id="name" name="name" type="text" required disabled={isPending} />
</div>
<div>
<label htmlFor="email">メールアドレス</label>
<input id="email" name="email" type="email" required disabled={isPending} />
</div>
<div>
<label htmlFor="message">メッセージ</label>
<textarea id="message" name="message" required disabled={isPending} rows={5} />
</div>
<button type="submit" disabled={isPending}>
{isPending ? "送信中..." : "送信する"}
</button>
{state.message && (
<p style={{ color: state.success ? "green" : "red" }}>{state.message}</p>
)}
</form>
);
}
何が起きているのか
ポイントは2つです。
① <form action={関数}> で送信処理を渡す
onSubmit の代わりに action 属性に関数を渡せるようになりました。
e.preventDefault() も不要、引数として FormData が自動で渡ってきます。input の name 属性さえあれば、formData.get("name") で値を取れます。
② useActionState が状態を一括で面倒みてくれる
const [state, formAction, isPending] = useActionState(submitContact, 初期値);
これ1行で、
-
state: アクションの結果(成功メッセージ・エラーメッセージなど) -
formAction: form のactionに渡す関数 -
isPending: 送信中かどうか(自動でtrue/falseが切り替わる)
の3つがまとめて返ってきます。useState を4つ作る必要が、まるごと無くなる ということです。
Before / After 比較
| 項目 | Before(React 18 以前) | After(React 19) |
|---|---|---|
useState の数 |
4個 | 0個 |
handleChange |
必要(各入力ごとに state 更新) | 不要(uncontrolled でOK) |
e.preventDefault() |
必要 | 不要 |
| ローディング状態 | 手動で setIsSubmitting(true/false)
|
自動(isPending) |
input の value / onChange
|
全 input に書く |
不要(name 属性だけでOK) |
行数で言うと体感3割減、それ以上に 「フォーム処理の本質」だけが残った 感覚があります。submitContact 関数を見れば、このフォームが何をしているか一目瞭然。
…これ、知らないと一生 useState を4個作り続ける気がします。
しかも今回試したのは GPT-5.5、つまり 最新の AI でもこの書き方を返してくる わけで、自分から「React 19 で書いて」と指示しない限り、ずっと古いままのコードを書き続けることになります。
🔍 なぜ AI は古い書き方を返すのか ── 調べてみた
最新の GPT-5.5 が、なぜ React 17 時代の書き方を返してくるのか。
ただ「AIあるある」で終わらせるのも気持ち悪かったので、少し調べてみました。
1. 学習データのカットオフ自体は問題ないはず
OpenAI のドキュメントによると、GPT-5.5 の学習データのカットオフは 2025年12月1日(Dec 01, 2025)です。
一方で、React 19 の正式リリースは 2024年12月5日。
つまり、GPT-5.5 のカットオフ時点では、React 19 が出てから ちょうど1年経過しています。理論上は、useActionState や <form action={...}> の存在を「知っている」はず なんですよね。
…なのに、知らない人みたいなコードを返してくる。なぜなのか。
2. 学習データの中で「古い書き方」が圧倒的多数派
これが本質的に一番大きな要因だと思います。
ネット上のコード資産 ── GitHub のリポジトリ、StackOverflow の回答、技術ブログ、Qiita 記事 ── これらの大半は、React 19 が出る前に書かれたものです。当然、フォームは useState + onSubmit で書かれています。
LLM は、学習データの中で 「よく出てくるパターン」を高頻度で再現する 性質があります。プロンプトに「最新の」「React 19 で」のような指示がなければ、自然と多数派の書き方が出力されます。
- 古い書き方のサンプル数: 膨大(React 16, 17, 18 時代の蓄積)
- 新しい書き方のサンプル数: 少ない(React 19 リリースから1〜2年分)
確率的に古い書き方が出てくるのは、ある意味当然です。
3. 公称カットオフと「実際に学習に効いたデータ」にギャップがある
もう一つの可能性。
「カットオフ日 = 2025年12月1日」と表示されていても、それは「この日付までのデータが含まれている」というだけで、直前のデータが学習に十分寄与しているとは限らない という指摘が、最近の研究や開発者コミュニティで出ています。
カットオフ日付近のデータは、量も質も薄くなる傾向がある。React 19 リリース直後のブログ・コード・解説記事も、まだ十分な量が出揃う前にカットオフされた可能性があります。
4. これは構造的な問題で、研究でも実証されている
2026年4月に発表された arXiv 論文 "When LLMs Lag Behind: Knowledge Conflicts from Evolving APIs in Code Generation" (Ashik et al., 2026) では、まさにこの問題 ── LLM がライブラリの進化に追いつけず、deprecated な API を生成し続ける問題 ── が定量的に研究されています。
タイトルがすべて言い尽くしている感じですが、要するに 「LLM はライブラリの進化から構造的に遅れる」 という現象は、自分が個人的に感じた違和感ではなく、学術的に認められた現象なんです。
つまり、こういうことらしい
整理すると、AI が古い書き方を返してくる理由は以下のとおりです。
| 要因 | 内容 |
|---|---|
| ① 学習データの量的偏り | ネット上に React 18 以前のコードの方が圧倒的に多い |
| ② カットオフと実態のギャップ | カットオフ日付近のデータは収録が薄い傾向 |
| ③ 多数派バイアス | LLM は確率的に「よく見るパターン」を優先して出力する |
| ④ ライブラリ進化への追従遅れ | 学術的にも構造的な問題として実証されている |
つまり、これは GPT-5.5 がたまたまサボってるとか、自分の運が悪かったとかではない。
LLM という仕組みに内在する 構造的な問題 です。
新しいフレームワーク、新しい書き方ほど、AI は「知ってはいるけど、自然には出してこない」状態になりやすい。React 19 はその典型例にハマってしまっている、ということになります。
💡 これからどう向き合うか
「AI の仕組み的に古いコードが出てくるのは仕方ない」と分かった上で、では自分はどうしていくか。
ジュニアエンジニアなりに、これからやっていこうと思っていることをいくつか書きます。
1. プロンプトに「最新の React で」を明示する
一番手っ取り早い対策です。
ただ「React でフォームを書いて」だと古い書き方が返ってきますが、最初から「React 19 の useActionState と form action を使って書いて」と指示すれば、ちゃんと新しい書き方を返してきます。
つまり、AI に任せるなら、「こちらが最新を知っている」ことが前提 になります。
(余談ですが、これに気づくと「AI に丸投げ」がそもそも無理ゲーだったことが分かります。何が最新か分かる人だけが、AI を正しく使える。)
2. AI が書いたコードに「疑いの目」を持つ
これは前回記事「就活中のジュニアエンジニアが面接で痛感した『力不足』の話」の続きでもありますが、AIが書いたコードを「動いたからOK」で済ませない。
特に怪しいパターンは、
-
useStateが4個も5個も並んでいる -
forwardRefやuseImperativeHandleのような複雑な仕組みを使っている - データ取得が
useEffectの中で書かれている
これらを見たら一回、「これって2026年の React 的に、まだベストな書き方なのか?」 と立ち止まる。完全に分かってなくても、「疑う」だけならジュニアでもできる ことです。
3. 「進化していること」を前提に扱う
これが一番大事かもしれません。
経験が浅い自分は、勉強し始めた頃に「React の書き方はこれが正解」と固定的に学んでいました。でも、フレームワークもライブラリも、毎年のように進化しています。
「今の自分の書き方は、いつか必ず古くなる」 ── この前提を持っておけば、定期的にキャッチアップする習慣を作るしかなくなります。
AI は便利ですが、AI 自身は進化に追いつくのが構造的に遅い。
だから 人間側が、ライブラリの進化に対する解像度を持っておく 必要がある、ということだと思います。
🙌 おわりに
今回の気づきをまとめると、こんな感じです。
- AI に React を書かせると、最新モデルでも古い書き方が返ってくる
- それは GPT-5.5 がサボってるからではなく、LLM の構造的な問題
- だから AI に丸投げできず、自分が「最新を知っている」状態を作るしかない
正直、最初は「AI を使えば、自分が知らないことでも書けるじゃん」と思っていました。でも調べていくうちに、それは半分くらい幻想だったことが分かりました。
AI が代わりに勉強してくれるわけではないし、むしろ AI が古い書き方を再生産する仕組みである以上、人間の側が新しさを学び続けないと、書くコードはどんどん古くなっていく ということなんですよね。
経験が浅いジュニアエンジニアこそ、ここを意識しないと取り残されるかもしれません。
同じように「AI に書かせて動いたから、それが React の書き方」と思っていた方、一緒に地道にキャッチアップしていきましょう💪
最後まで読んでいただき、ありがとうございました!