Open4
react-hook-formとzodとmuiを組み合わせる方法メモ
このSandbox上に構築していく
環境
name | version |
---|---|
react | 18.2.0 |
react-hook-form | 7.49.2 |
zod | 3.22.4 |
@mui/material | 5.15.0 |
より良い記述方法があればアップデートしていきたいので、コメント歓迎です〜!
nameとemailのテキストの入力要素があるフォームはこのようにFormを書きたい
ExampleForm.tsx
import z from "zod";
import { Box, Button } from "@mui/material";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import TextFieldWithController from "../parts/TextFieldWithController";
const schema = z.object({
name: z.string().trim().min(1),
email: z.string().email(),
});
export type SchemaType = z.infer<typeof schema>;
const defaultValues: SchemaType = {
name: "",
email: "",
};
type Props = {
onSubmit: (data: SchemaType) => void;
};
export default function ExampleForm({ onSubmit }: Props) {
const { control, handleSubmit } = useForm({
resolver: zodResolver(schema),
defaultValues,
});
const onInvalid = (err: unknown) => {
console.error(err);
};
return (
<Box
sx={{ display: "flex", flexDirection: "column" }}
component="form"
onSubmit={handleSubmit(onSubmit, onInvalid)}
>
<TextFieldWithController<SchemaType>
name="name"
control={control}
label="name"
/>
<TextFieldWithController<SchemaType>
name="email"
control={control}
label="email"
/>
<Button type="submit">Submit</Button>
</Box>
);
}
上記FormのTextFieldWithControllerコンポーネントは下記のように実装する。
TextFieldWithController.tsx
import { TextField, TextFieldProps } from "@mui/material";
import { Controller, Control, FieldValues, FieldPath } from "react-hook-form";
type Props<T extends FieldValues> = TextFieldProps & {
control: Control<T>;
name: FieldPath<T>;
};
export default function TextFieldWithController<T extends FieldValues>({
name,
control,
...textFieldProps
}: Props<T>) {
return (
<Controller
name={name}
control={control}
render={({ field, formState: { errors } }) => {
const error = name in errors;
const errorText = errors[name]?.message as string;
return (
<TextField
{...textFieldProps}
{...field}
error={error}
helperText={errorText}
/>
);
}}
/>
);
}
別のカスタムしたTextFieldをCustomTextFieldとして下記のように定義し、
CustomTextField.tsx
import { TextField, styled } from "@mui/material";
export default styled(TextField)(() => ({
"& fieldset": {
// ここにfieldsetのスタイル
},
"& input": {
// ここにinputのスタイル
},
"& label": {
// ここにlabelのスタイル
}
}));
form側で下記のようにcomponentプロパティを使ってスタイルを上書きして使うこともできる。
ExampleForm.tsx
<TextFieldWithController<SchemaType>
+ component={CustomTextField}
name="name"
control={control}
label="name"
/>
よく使うTextFieldのコンポーネントがある場合はTextFieldWithController側でdefault propsとして定義することもできる
TextFieldWithController.tsx
import { TextField, TextFieldProps } from "@mui/material";
import { Controller, Control, FieldValues, FieldPath } from "react-hook-form";
import CustomTextField from "./CustomTextField";
type Props<T extends FieldValues> = TextFieldProps & {
control: Control<T>;
name: FieldPath<T>;
};
export default function TextFieldWithController<T extends FieldValues>({
+ component = CustomTextField,
name,
control,
...textFieldProps
}: Props<T>) {
return (
<Controller
name={name}
control={control}
render={({ field, formState: { errors } }) => {
const error = name in errors;
const errorText = errors[name]?.message as string;
return (
<TextField
+ component={component}
{...textFieldProps}
{...field}
error={error}
helperText={errorText}
/>
);
}}
/>
);
}
...
次はRadioGroup編