😽

react-fook-formとmuiを連携して型安全に使う

2023/12/31に公開

はじめに

react-fook-formとmuiを連携したTextField作成時に型安全に扱いたい場合の忘備録

型補完の効かないコード

前提として、muiからimportしたTextFieldはコンポーネントとして切り出します。
それぞれ使いた場所で呼び出す形にします。

InputTextField
import { TextField, TextFieldProps } from "@mui/material";
import { Controller, useFormContext } from "react-hook-form";

type Props = {
    name: string;
} & TextFieldProps;
export const InputTextField = ({ name, ...rest }: Props) => {
  const { control } = useFormContext();

  return (
    <Controller
      name={name}
      control={control}
      render={({ field }) => <TextField {...field} {...rest} />}
      )}
    />
  );
};

このままだと呼び出し先では、nameに渡す値は文字列なので補完が効きません

index
import { InputTextField } from "@/components/InputTextField";
import { Button } from "@mui/material";
import { SubmitHandler, useFormContext } from "react-hook-form";

export type FormValues = {
  text: string;
};

type Props = {
  onSubmit: SubmitHandler<FormValues>;
};

export const PopupForm = ({ onSubmit }: Props) => {
  const { handleSubmit } = useForm<FormValues>();

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <InputTextField
        name=""
      />
      <Button type="submit" color="primary" variant="contained">
        確定
      </Button>
    </form>
  );
};

型補完が効くコード

InputTextFieldコンポーネントをこのように型定義してあげましょう

TextField
type Props<T extends FieldValues> = {
  name: Path<T>;
  control: Control<T>;
} & TextFieldProps;

export const TextField = <T extends FieldValues>({
  name,
  control,
  ...rest
}: Props<T>) => {
  return (
    <Controller
      name={name}
      control={control}
      render={({ field }) => <MuiTextField {...field} {...rest} />}
    />
  );
};

これで呼び出し先で型補完が効くので開発効率アップですね
FormValuesで定義していない値をnameで渡そうとすると怒られます

index
import { InputTextField } from "@/components/InputTextField";
import { Button } from "@mui/material";
import { SubmitHandler, useFormContext } from "react-hook-form";

export type FormValues = {
  text: string;
};

type Props = {
  onSubmit: SubmitHandler<FormValues>;
};

export const PopupForm = ({ onSubmit }: Props) => {
  const { control, handleSubmit } = useForm<FormValues>();

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <InputTextField
        name="text"
        control={control}
      />
      <Button type="submit" color="primary" variant="contained">
        確定
      </Button>
    </form>
  );
};

Discussion