React で controlled / uncontrolled input 警告が消せない
「A component is changing an uncontrolled input to be controlled」警告は、value プロップが undefined と確定値の間で切り替わると出る。
初期値を必ず空文字や null 以外で与え、value / defaultValue を混在させないのが原則。
#react#input#form#controlled#uncontrolled#warning
公開:
要約
React の <input> は 制御 (controlled) と 非制御 (uncontrolled) のどちらかで一貫している必要がある。
途中で value が undefined ↔ 文字列の間を行き来すると、「changing an uncontrolled input to be controlled」警告が出る。
公式の <input> リファレンス のとおり、最初から非 undefined の値で渡せば解決する。
よくある原因
- 初期 state が undefined:
useState()を引数なしで呼ぶと初期値はundefined。
最初のレンダーでvalue={undefined}→ 入力後に文字列、と切り替わる - 非同期データの流し込み:
useEffectでsetValue(data.name)する場合、初回レンダー時はdataが無くundefined - value と defaultValue の併用: 同じ input に両方渡すと React がどちらに従うか決められない
- checked が undefined: checkbox / radio で
checkedを Boolean 化せずに渡している
解決策
1. 初期値を必ず与える
// NG
const [name, setName] = useState();
// OK
const [name, setName] = useState("");input は文字列、checkbox は Boolean、select は文字列 / 数値で空でも明示する。
2. 非同期値はフォールバック
const { data } = useQuery(...);
return (
<input
value={data?.name ?? ""}
onChange={(e) => mutate(e.target.value)}
/>
);?? で undefined のときだけ空文字に落とす。|| だと 0 のような falsy 値も落ちるので注意。
3. 制御 / 非制御を統一
// 制御コンポーネント
<input value={value} onChange={(e) => setValue(e.target.value)} />
// 非制御コンポーネント
<input defaultValue="初期値" ref={inputRef} />両方混ぜない。
フォーム全体を useState で扱うなら defaultValue は使わず value で統一する。
4. checkbox の Boolean 化
<input
type="checkbox"
checked={Boolean(item.done)}
onChange={(e) => toggle(e.target.checked)}
/>item.done が undefined でも Boolean(undefined) は false で安定する。
同じ思想で radio の checked も Boolean に変換する。