とりまとめてチェックボックスの値をFormikで更新する方法

## はじめに
フォームの作成において、あるチェックボックスのON/OFFを、他のチェックボックスにも反映させたい場面は珍しくないかと思います。

今回はFormikを使ったフォームの作成において、チェックボックスのチェックを他のチェックボックスにも反映させる方法を紹介します。

## 概要
以下のような画面があるとします。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/7c814154-2fde-b736-b942-1863ec3439a1.png'>

「すべて選択」をチェックしたときには、「オプション1」〜「オプション3」までのチェックも連動して変わるようにしたいです。

## 実装方法
### 結論
先に結論から書くと、実装は以下のように行います。

```typescript
  // 「すべて」が選択されたときの処理
  const handleSelectAll = (checked: boolean) => {
    formik.setValues({
      ...formik.values,
      selectAll: checked,
      selectedOptions: checked ? options.map((option) => option.value) : [],
    });
  };
```

ポイントは2つです。

### setValuesを使う
`formik`の`setValues`は、引数に設定された値でフォーム全体の値を更新します。
これにより、関連する値をまとめて更新することができます。

### スプレッド構文を使う
`setValues`は設定された値で丸ごと更新してしまいます。
今回のように、複数のフォームが設定されている場面で、チェックボックスのみを`setValues`に指定してしまうと、他のフォームの値が消えてしまいます。

そこで使用するのがスプレッド構文です。
冒頭に示した例で言うところの`...formik.values`がスプレッド構文にあたります。
`formik.values`にはフォームのオブジェクトがすべて設定されているため、これを利用することで、フォームオブジェクトを取りこぼすことなく設定できます。

そして、その後にカンマ区切りで変更したいパラメータのみ設定します。
これにより、フォームのオブジェクトは維持しつつ、特定のプロパティのみ書き換えることができます。

このテクニックは`formik`に限らず、オブジェクトを使う場面ならどこでも利用できます。
オブジェクトの特定のプロパティのみ書き換えたいときにはスプレッド構文を使うとよいです。

### コード全容
今回の実装のすべてを以下に示します。
`setValues`の他にも、`getFieldProps`など、`formik`を扱う上で便利なものを使っているのでぜひ確認してみてください。

また、フォームの構成においてはMaterial-UIも使用しています。

```react
import { useFormik } from "formik";
import {
  Checkbox,
  FormControlLabel,
  FormGroup,
  Button,
  TextField,
  Radio,
  RadioGroup,
} from "@mui/material";

// フォームの要素を型として定義
interface FormValues {
  selectAll: boolean;
  selectedOptions: string[];
  textInput: string;
  radioOption: string;
}

// チェックボックス用配列
const options = [
  { value: "option1", label: "オプション1" },
  { value: "option2", label: "オプション2" },
  { value: "option3", label: "オプション3" },
];

// ラジオボタン用配列
const radioOptions = [
  { value: "radio1", label: "ラジオ1" },
  { value: "radio2", label: "ラジオ2" },
  { value: "radio3", label: "ラジオ3" },
];

// フォームの初期値
const initialValues: FormValues = {
  selectAll: false,
  selectedOptions: [],
  textInput: "",
  radioOption: "",
};

const App = () => {
  // formikの設定
  const formik = useFormik({
    initialValues,
    onSubmit: (values) => {
      console.log(values);
    },
  });

  // 「すべて」の選択状態
  const allSelected = formik.values.selectedOptions.length === options.length;

  // 「すべて」が選択されたときの処理
  const handleSelectAll = (checked: boolean) => {
    formik.setValues({
      ...formik.values,
      selectAll: checked,
      selectedOptions: checked ? options.map((option) => option.value) : [],
    });
  };

  return (
    <form onSubmit={formik.handleSubmit}>
      <TextField
        fullWidth
        label="テキスト入力"
        {...formik.getFieldProps("textInput")}
        margin="normal"
      />

      <RadioGroup
        aria-label="radio-options"
        {...formik.getFieldProps("radioOption")}
      >
        {radioOptions.map((option) => (
          <FormControlLabel
            key={option.value}
            value={option.value}
            control={<Radio />}
            label={option.label}
          />
        ))}
      </RadioGroup>

      <FormGroup>
        <FormControlLabel
          control={
            <Checkbox
              checked={allSelected}
              onChange={(e) => handleSelectAll(e.target.checked)}
              name="selectAll"
            />
          }
          label="すべて選択"
        />
        {options.map((option) => (
          <FormControlLabel
            key={option.value}
            control={
              <Checkbox
                {...formik.getFieldProps("selectedOptions")}
                value={option.value}
                checked={formik.values.selectedOptions.includes(option.value)}
              />
            }
            label={option.label}
          />
        ))}
      </FormGroup>

      <Button
        type="submit"
        variant="contained"
        color="primary"
        style={{ marginTop: "1rem" }}
      >
        送信
      </Button>
    </form>
  );
};

export default App;
```

## まとめ
単に画面上のチェックボックスをON/OFFするだけなら、`state`でも実装は可能ですが、フォームに対して状態が変わったことを通知するためには今回のようにフォームに対して値を書き換えて上げる必要があります。

その方法の一つとして`setValues`は一括での更新が可能なので、もっと他にも利用方法がありそうだと感じました。