🐥
ReactのinputとStateで「controlled/uncontrolled」エラーを解決する実践ガイド
はじめに
Reactでフォームを作成していると、こんなエラーに遭遇したことはありませんか?
A component is changing an uncontrolled input to be controlled.
This is likely caused by the value changing from undefined to a defined value, which should not happen.
この記事では、実際の開発で起こりがちなinputとStateの問題を、具体的なコード例とともに解決方法を紹介します。
問題の発生パターン
パターン1: valueが設定されていない
// ❌ よくある間違い
<input
type="text"
id="username"
name='username'
// value={userInfo.username} ← これが抜けている
onChange={(e) => setUserInfo({...userInfo, username: e.target.value})}
/>
パターン2: ラジオボタンの一部だけcontrolled
// ❌ 20代だけcontrolled、他はuncontrolled
<input type="radio" name="userage" checked={userInfo.userage === "20"} />
<input type="radio" name="userage" value="30" /> // checkedがない
<input type="radio" name="userage" value="40" /> // checkedがない
解決方法
1. 基本的なtext inputの正しい書き方
const [userInfo, setUserInfo] = useState({
username: "",
usermail: "",
userage: "",
});
// ✅ 正しい書き方
<input
type="text"
id="username"
name='username'
value={userInfo.username} // ← 必須!
onChange={(e) => setUserInfo({...userInfo, username: e.target.value})}
/>
2. ラジオボタンの効率的な実装
繰り返しコードを避けるため、配列とmapを使用します:
// 選択肢を配列で定義(関数の外)
const ageOptions = [
{ value: "20", label: "20代" },
{ value: "30", label: "30代" },
{ value: "40", label: "40代" },
{ value: "50", label: "50代" },
{ value: "60", label: "60代" },
{ value: "over", label: "それ以上" }
];
const handleAgeChange = (value) => {
setUserInfo({
...userInfo,
userage: value
});
};
// JSX
<div>
{ageOptions.map((option) => (
<label key={option.value} htmlFor={`userage-${option.value}`}>
<input
type="radio"
id={`userage-${option.value}`}
name="userage"
value={option.value}
checked={userInfo.userage === option.value}
onChange={(e) => handleAgeChange(e.target.value)}
/>
<span>{option.label}</span>
</label>
))}
</div>
3. 必須項目のバリデーション
// リアルタイムエラー表示
{!userInfo.userage && (
<p className="text-red-500 text-sm">年齢の選択は必須です</p>
)}
Computed Property Names(計算プロパティ名)
複数の質問がある場合、動的なキーでstateを管理します:
const [answers, setAnswers] = useState({});
const handleAnswerChange = (questionId, selectedOption) => {
setAnswers({
...answers,
[questionId]: selectedOption // ← []が必要!
});
};
// 使用例
{data.questions.map((question) => (
<div key={question.id}>
<h2>{question.id}. {question.question}</h2>
{question.options.map((option) => (
<label key={option.id}>
<input
type="radio"
name={`question-${question.id}`}
value={option.id}
checked={answers[question.id] === option.id}
onChange={() => handleAnswerChange(question.id, option.id)}
/>
{option.text}
</label>
))}
</div>
))}
[questionId]
に[]が必要?
なぜ// ✅ []があると変数の値をキーに使用
const questionId = "1";
setAnswers({
...answers,
[questionId]: selectedOption // 結果: { "1": "a" }
});
// ❌ []がないと文字通り"questionId"がキーになる
setAnswers({
...answers,
questionId: selectedOption // 結果: { "questionId": "a" }
});
ベストプラクティス
1. 定数は関数の外で定義
// ✅ パフォーマンスが良い
const ageOptions = [...]; // 関数の外
export default function Component() {
// ロジックに集中
}
2. keyは意味のある値を使用
// ✅ 推奨
key={option.value} // "male", "female", "other"
// ❌ 避ける
key={index} // 0, 1, 2
3. keyは最も外側の要素に設定
// ✅ 正しい
{options.map((option) => (
<label key={option.value}> {/* 最も外側 */}
<input ... />
</label>
))}
// ❌ 間違い
{options.map((option) => (
<div> {/* keyがない */}
<label key={option.value}> {/* 内側にある */}
まとめ
-
controlled input:
value
とonChange
の両方が必要 - ラジオボタン: 同じnameグループは全て同じ方式で統一
- 効率化: 配列とmapで繰り返しコードを削減
-
動的キー:
[変数名]
でComputed Property Namesを活用 - パフォーマンス: 定数は関数外で定義
これらのパターンを覚えておけば、Reactのフォームで困ることは大幅に減るでしょう!
Discussion