かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

React checkbox の checked と defaultChecked

React で checkbox の状態を別のところにある label で制御していて warning が出ていた

const Component = () => {
  const [isActive, setIsActive] = useState<boolean>(true);
  const handleOnActive = () => setIsActive(true);
  const handleDeActive = () => setIsActive(false);
  return (
    <div>
      <input id="myCheckbox" type="checkbox" checked={isActive} />
      <div className="tab">
        <label htmlFor="myCheckbox" onClick={handleOnActive}>Tab 1</label>
        <label htmlFor="myCheckbox" onClick={handleDeActive}>Tab 2</label>
      </div>
      <div className="tabContent">
        <div className={`content1 ${isActive ? 'show' : 'hide'}`} aria-hidden={!isActive}>content 1</div>
        <div className={`content2 ${isActive ? 'hide' : 'show'}`} aria-hidden={isActive}>content 2</div>
      </div>
    </div>
  );
};

You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.

環境
  • React 18.2.0
  • TypeScript 5.2.2

Controlled Component には onChangereadOnly が必要

value や checked という状態を state 制御している Controlled Components は直接 state を変更できる onChange を付けるか別のところで state を変更する場合は readOnly にしておく必要がある

今回のケースだと label のクリックで checkbox の cheked (状態) を変更したいので readOnly を付ける必要があった

const Component = () => {
  const [isActive, setIsActive] = useState<boolean>(true);
  const handleOnActive = () => setIsActive(true);
  const handleDeActive = () => setIsActive(false);
  return (
    <div>
-     <input id="myCheckbox" type="checkbox" checked={isActive} />
+     <input id="myCheckbox" type="checkbox" checked={isActive} readOnly />
      <div className="tab">
        <label htmlFor="myCheckbox" onClick={handleOnActive}>Tab 1</label>
        <label htmlFor="myCheckbox" onClick={handleDeActive}>Tab 2</label>
      </div>
      {/* 略 */}
    </div>
  );
};

Checkbox / Radio の checkeddefaultChecked

  • checkbox / radio のチェック状態は value ではなく checked で管理する。(デフォルト値は defaultValue でなく defaultChecked を使う)
  • checkeddefaultChecked は同時には使えない
    • 同時に使用すると warning が発生する
      • Component contains an input of type checkbox with both checked and defaultChecked props. Input elements must be either controlled or uncontrolled (specify either the checked prop, or the defaultChecked prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props.
    • checked がある場合はチェックの状態は state で制御されるのでデフォルト値を設定する必要がない
    • チェック状態を state で制御しない 非制御コンポーネント (Uncontrolled Components) として扱う場合は defaultChecked のみを使う

先のコンポーネントの check 状態を通常の HTML のように for を使った label で変更させるだけであれば defaultChecked を使った非制御コンポーネントにすることもできる

const UnControlledComponent = () => {
  return (
    <div>
      <input id="myCheckbox" type="checkbox" defaultChecked={true} />
      <div className="tab">
        <label htmlFor="myCheckbox">Tab 1</label>
        <label htmlFor="myCheckbox">Tab 2</label>
      </div>
      <div className="tabContent">
        {/* state を使わないので表示の切り替えは CSS で行う */}
        <div className="content1">content 1</div>
        <div className="content2">content 2</div>
      </div>
    </div>
  );
};

まとめ

  • Radiobox / Radio のコンポーネントで state を使う必要があるのかどうかで Controlled Components にするか Uncontrolled Components を決める
  • Uncontrolled Components なら defaultChecked を使う
  • Controlled Components なら checked で状態を state で扱う
    • 直接変更するには onClick を渡して state を変更できるようにする
    • label など別の要素から state を変更する場合は Checkbox / Radio には readOnly を付ける

[参考]