React Hook Form v7でMaterial UI v4のフォームを制御する

React
スポンサーリンク
language

今回はReact Hook Form v7でMaterial UI v4のフォームを制御する方法をご紹介します。

Material UIのTextFieldやRadio、Selectは素直に動いてくれますが、Checkboxはちょっとしたコツ、工夫が必要です。
サンプルコードをご紹介しますので是非参考にしてください。

また、 React Hook Formからのフォームのエラーテキストを表示するサンプルコードも合わせてご紹介しています。

  • React Hook Form v7.12.0
  • Material-UI v4.12.2
スポンサーリンク

TextField

React Hook Form v7で制御したMaterial UI v4 のTextFieldは以下の通りです。
サンプルコードの通り、TextFieldは素直に動いてくれます。

TextFieldを制御するサンプルコードは以下の通りです。

import React, { useState } from "react";
import { useForm, FieldValues, Controller } from "react-hook-form";
import Button from "@material-ui/core/Button";
import FormHelperText from "@material-ui/core/FormHelperText";
import FormControl from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";
import TextField from "@material-ui/core/TextField";

export default function App() {
  const [submitData, setSubmitData] = useState<string>("");

  const {
    formState: { errors },
    control,
    handleSubmit
  } = useForm<FieldValues>({
    criteriaMode: "all"
  });

  const handleFormOnSubmit = (data: any) => {
    setSubmitData(data.text);
  };

  return (
    <>
      <form
        method="POST"
        onSubmit={handleSubmit(handleFormOnSubmit)}
        encType="multipart/form-data"
      >
        <FormControl
          required
          error={errors?.hasOwnProperty("text")}
          component="fieldset"
          fullWidth
        >
          <FormLabel component="legend">テキスト質問</FormLabel>

          <FormHelperText>
            {errors?.text && errors?.text.message}
          </FormHelperText>
          <Controller
            name="text"
            control={control}
            rules={{ required: "入力してください" }}
            render={({ field }) => <TextField {...field} variant="outlined" />}
          />
        </FormControl>

        <Button type="submit" variant="contained" color="primary">
          送信
        </Button>
      </form>
      <br />
      送信内容
      <br />
      {submitData}
    </>
  );
}

Checkbox

React Hook Form v7でMaterial UI v4 のCheckboxを制御するにはコツが要ります。
以下の React Hook Form v7で制御したMaterial UI v4 のCheckbox は次の仕様となっています。

  • 初期値(デフォルトチェック)に対応
  • チェックボックスの値の変更を制御
  • Material UIを使ってCheckboxグループを設置(同じnameで複数設置)

Checkboxを制御するサンプルコードは以下のとおりです。

import React, { useState } from "react";
import { useForm, FieldValues, Controller } from "react-hook-form";
import Button from "@material-ui/core/Button";
import Checkbox from "@material-ui/core/Checkbox";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormGroup from "@material-ui/core/FormGroup";
import FormHelperText from "@material-ui/core/FormHelperText";
import FormControl from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";

export default function App() {
  const [submitData, setSubmitData] = useState<string>("");
  const defaultCheckedValues = ["B"];

  // Options
  const options = [
    {
      title: "A"
    },
    {
      title: "B"
    },
    {
      title: "C"
    },
    {
      title: "D"
    }
  ];
  const {
    formState: { errors },
    control,
    setValue,
    getValues,
    handleSubmit
  } = useForm<FieldValues>({
    criteriaMode: "all",
    defaultValues: { checkbox: defaultCheckedValues.join(",") }
  });

  const handleFormOnSubmit = (data: any) => {
    setSubmitData(data.checkbox);
  };

  const handleCheck = (
    option: { title: string },
    event: React.ChangeEvent<{}>
  ) => {
    let values: string[] = getValues("checkbox")?.split(",") || [];
    values = values.filter((v) => v); // 空要素削除

    let newValues: string[] = [];
    if ((event.target as HTMLInputElement).checked) {
      newValues = [...(values ?? []), option.title];
    } else {
      newValues = values?.filter((value) => value !== option.title);
    }
    setValue("checkbox", newValues.join(","));

    return newValues.join(",");
  };

  return (
    <>
      <form
        method="POST"
        onSubmit={handleSubmit(handleFormOnSubmit)}
        encType="multipart/form-data"
      >
        <FormControl
          required
          error={errors?.hasOwnProperty("checkbox")}
          component="fieldset"
          fullWidth
        >
          <FormLabel component="legend">質問</FormLabel>

          <FormHelperText>
            {errors?.checkbox && errors?.checkbox.message}
          </FormHelperText>

          <FormGroup>
            <Controller
              name="checkbox"
              control={control}
              rules={{ required: "選択してください。" }}
              defaultValue=""
              render={({ field, fieldState }) => (
                <>
                  {options.map((option, i) => (
                    <FormControlLabel
                      {...field}
                      key={i}
                      label={option.title}
                      onChange={(event) =>
                        field.onChange(handleCheck(option, event))
                      }
                      control={
                        <Checkbox
                          defaultChecked={defaultCheckedValues.includes(
                            option.title
                          )}
                        />
                      }
                    />
                  ))}
                </>
              )}
            />
          </FormGroup>
        </FormControl>
        <Button type="submit" variant="contained" color="primary">
          送信
        </Button>
      </form>
      <br />
      送信内容
      <br />
      {submitData}
    </>
  );
}

また、 React Hook Form v7でMaterial UI v4 のCheckboxを制御する方法は以下の記事で詳しく解説しています。

Select

React Hook Form v7で制御したMaterial UI v4 のSelectは以下の通りです。
サンプルコードの通り、Selectは素直に動いてくれます。

Selectを制御するサンプルコードは以下の通りです。

import React, { useState } from "react";
import { useForm, FieldValues, Controller } from "react-hook-form";
import Button from "@material-ui/core/Button";
import FormHelperText from "@material-ui/core/FormHelperText";
import FormControl from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";

export default function App() {
  const [submitData, setSubmitData] = useState<string>("");

  // Options
  const options = [
    {
      title: "A"
    },
    {
      title: "B"
    },
    {
      title: "C"
    },
    {
      title: "D"
    }
  ];
  const {
    formState: { errors },
    control,
    handleSubmit
  } = useForm<FieldValues>({
    criteriaMode: "all"
  });

  const handleFormOnSubmit = (data: any) => {
    setSubmitData(data.select);
  };

  return (
    <>
      <form
        method="POST"
        onSubmit={handleSubmit(handleFormOnSubmit)}
        encType="multipart/form-data"
      >
        <FormControl
          required
          error={errors?.hasOwnProperty("select")}
          component="fieldset"
          fullWidth
        >
          <FormLabel component="legend">セレクト質問</FormLabel>

          <FormHelperText>
            {errors?.select && errors?.select.message}
          </FormHelperText>
          <Controller
            name="select"
            control={control}
            rules={{ required: "選択してください。" }}
            render={({ field, fieldState }) => (
              <Select {...field}>
                {options.map((option, i) => (
                  <MenuItem value={option.title} key={i}>
                    {option.title}
                  </MenuItem>
                ))}
              </Select>
            )}
          />
        </FormControl>
        <Button type="submit" variant="contained" color="primary">
          送信
        </Button>
      </form>
      <br />
      送信内容
      <br />
      {submitData}
    </>
  );
}

RadioGroup

React Hook Form v7で制御したMaterial UI v4 のRadioGroupは以下の通りです。
サンプルコードの通り、 RadioGroupも素直に動いてくれます。

RadioGroupを制御するサンプルコードは以下のとおりです。

import React, { useState } from "react";
import { useForm, FieldValues, Controller } from "react-hook-form";
import Button from "@material-ui/core/Button";
import FormHelperText from "@material-ui/core/FormHelperText";
import FormControl from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";
import Radio from "@material-ui/core/Radio";
import RadioGroup from "@material-ui/core/RadioGroup";
import FormControlLabel from "@material-ui/core/FormControlLabel";
export default function App() {
  const [submitData, setSubmitData] = useState<string>("");

  // Options
  const options = [
    {
      title: "A"
    },
    {
      title: "B"
    },
    {
      title: "C"
    },
    {
      title: "D"
    }
  ];
  const {
    formState: { errors },
    control,
    handleSubmit
  } = useForm<FieldValues>({
    criteriaMode: "all"
  });

  const handleFormOnSubmit = (data: any) => {
    setSubmitData(data.radio);
  };

  return (
    <>
      <form
        method="POST"
        onSubmit={handleSubmit(handleFormOnSubmit)}
        encType="multipart/form-data"
      >
        <FormControl
          required
          error={errors?.hasOwnProperty("radio")}
          component="fieldset"
          fullWidth
        >
          <FormLabel component="legend">ラジオ質問</FormLabel>

          <FormHelperText>
            {errors?.radio && errors?.radio.message}
          </FormHelperText>
          <Controller
            name="radio"
            control={control}
            rules={{ required: "選択してください。" }}
            render={({ field, fieldState }) => (
              <RadioGroup {...field}>
                {options.map((option, i) => (
                  <FormControlLabel
                    value={option.title}
                    control={<Radio />}
                    label={option.title}
                    key={i}
                  />
                ))}
              </RadioGroup>
            )}
          />
        </FormControl>
        <Button type="submit" variant="contained" color="primary">
          送信
        </Button>
      </form>
      <br />
      送信内容
      <br />
      {submitData}
    </>
  );
}

まとめ

それぞれを1つのフォームにまとめると次のようにできます。

サンプルコードは以下のとおりです。

import React, { useState } from "react";
import { useForm, FieldValues, Controller } from "react-hook-form";
import Button from "@material-ui/core/Button";
import Checkbox from "@material-ui/core/Checkbox";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormGroup from "@material-ui/core/FormGroup";
import FormHelperText from "@material-ui/core/FormHelperText";
import FormControl from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";
import TextField from "@material-ui/core/TextField";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import Radio from "@material-ui/core/Radio";
import RadioGroup from "@material-ui/core/RadioGroup";

export default function App() {
  const [submitData, setSubmitData] = useState<string>("");
  const defaultCheckedValues = ["B"];

  // Options
  const options = [
    {
      title: "A"
    },
    {
      title: "B"
    },
    {
      title: "C"
    },
    {
      title: "D"
    }
  ];
  const {
    formState: { errors },
    control,
    setValue,
    getValues,
    handleSubmit
  } = useForm<FieldValues>({
    criteriaMode: "all",
    defaultValues: { checkbox: defaultCheckedValues.join(",") }
  });

  const handleFormOnSubmit = (data: any) => {
    setSubmitData(
      data.text + "、" + data.checkbox + "、" + data.select + "、" + data.radio
    );
  };

  const handleCheck = (
    option: { title: string },
    event: React.ChangeEvent<{}>
  ) => {
    let values: string[] = getValues("checkbox")?.split(",") || [];
    values = values.filter((v) => v); // 空要素削除

    let newValues: string[] = [];
    if ((event.target as HTMLInputElement).checked) {
      newValues = [...(values ?? []), option.title];
    } else {
      newValues = values?.filter((value) => value !== option.title);
    }
    setValue("checkbox", newValues.join(","));

    return newValues.join(",");
  };

  return (
    <>
      <form
        method="POST"
        onSubmit={handleSubmit(handleFormOnSubmit)}
        encType="multipart/form-data"
      >
        <FormControl
          required
          error={errors?.hasOwnProperty("text")}
          component="fieldset"
          fullWidth
          style={{ marginBottom: "30px" }}
        >
          <FormLabel component="legend">テキスト質問</FormLabel>

          <FormHelperText>
            {errors?.text && errors?.text.message}
          </FormHelperText>
          <Controller
            name="text"
            control={control}
            rules={{ required: "入力してください" }}
            render={({ field }) => <TextField {...field} variant="outlined" />}
          />
        </FormControl>

        <FormControl
          required
          error={errors?.hasOwnProperty("checkbox")}
          component="fieldset"
          fullWidth
          style={{ marginBottom: "30px" }}
        >
          <FormLabel component="legend">質問</FormLabel>

          <FormHelperText>
            {errors?.checkbox && errors?.checkbox.message}
          </FormHelperText>

          <FormGroup>
            <Controller
              name="checkbox"
              control={control}
              rules={{ required: "選択してください。" }}
              defaultValue=""
              render={({ field, fieldState }) => (
                <>
                  {options.map((option, i) => (
                    <FormControlLabel
                      {...field}
                      key={i}
                      label={option.title}
                      onChange={(event) =>
                        field.onChange(handleCheck(option, event))
                      }
                      control={
                        <Checkbox
                          defaultChecked={defaultCheckedValues.includes(
                            option.title
                          )}
                        />
                      }
                    />
                  ))}
                </>
              )}
            />
          </FormGroup>
        </FormControl>

        <FormControl
          required
          error={errors?.hasOwnProperty("select")}
          component="fieldset"
          fullWidth
          style={{ marginBottom: "30px" }}
        >
          <FormLabel component="legend">セレクト質問</FormLabel>

          <FormHelperText>
            {errors?.select && errors?.select.message}
          </FormHelperText>
          <Controller
            name="select"
            control={control}
            rules={{ required: "選択してください。" }}
            render={({ field, fieldState }) => (
              <Select {...field}>
                {options.map((option, i) => (
                  <MenuItem value={option.title} key={i}>
                    {option.title}
                  </MenuItem>
                ))}
              </Select>
            )}
          />
        </FormControl>

        <FormControl
          required
          error={errors?.hasOwnProperty("radio")}
          component="fieldset"
          fullWidth
          style={{ marginBottom: "30px" }}
        >
          <FormLabel component="legend">ラジオ質問</FormLabel>

          <FormHelperText>
            {errors?.radio && errors?.radio.message}
          </FormHelperText>
          <Controller
            name="radio"
            control={control}
            rules={{ required: "選択してください。" }}
            render={({ field, fieldState }) => (
              <RadioGroup {...field}>
                {options.map((option, i) => (
                  <FormControlLabel
                    value={option.title}
                    control={<Radio />}
                    label={option.title}
                    key={i}
                  />
                ))}
              </RadioGroup>
            )}
          />
        </FormControl>

        <Button type="submit" variant="contained" color="primary">
          送信
        </Button>
      </form>
      <br />
      送信内容
      <br />
      {submitData}
    </>
  );
}

Checkboxだけ制御するのに工夫が必要がですが、他は公式のサンプルコードの通り素直に動いてくれます。

React hook formを用いてMaterial UIのフォームを制御することで、見た目も美しくフォームの機能も充実します。

この記事でわからないコードなどがあれば、書籍で勉強するのがおすすめです。
プログラミングの技術の移り変わりは早いので最新版の書籍を購入することをお勧めします。
中古本を買って勉強したけど、今は使われない技術だったということはよくあります。

javascript・Reactでおすすめの書籍をご紹介します。

りーほー
りーほー

JavaScriptを基礎からしっかり学びたい方におすすめ!

りーほー
りーほー

JavaScriptを基礎から応用までガッツリ学びたい方におすすめ!

りーほー
りーほー

Reactの概念や仕組みを把握し、開発で使えるReactを学びたい方にオススメ!

タイトルとURLをコピーしました