できない.dev

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) のどちらかで一貫している必要がある。
途中で valueundefined ↔ 文字列の間を行き来すると、「changing an uncontrolled input to be controlled」警告が出る。
公式の <input> リファレンス のとおり、最初から非 undefined の値で渡せば解決する。

よくある原因

  1. 初期 state が undefined: useState() を引数なしで呼ぶと初期値は undefined
    最初のレンダーで value={undefined} → 入力後に文字列、と切り替わる
  2. 非同期データの流し込み: useEffectsetValue(data.name) する場合、初回レンダー時は data が無く undefined
  3. value と defaultValue の併用: 同じ input に両方渡すと React がどちらに従うか決められない
  4. 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.doneundefined でも Boolean(undefined)false で安定する。
同じ思想で radio の checked も Boolean に変換する。

この記事は役立ちましたか?