React + MUI + TS + React-hook-formで作るログインフォーム
今回作ったもの
デザインはMUIのテンプレお借りしました。Tyvm💛
9+ Free React Templates - Material UI
もちろん、会員登録ページなどにもお使いいただけます。
指定したバリデーションに引っかかるとこんな感じになります。
今回はvalidationライブラリを使っていません。
ちなみに緑の鍵マークは使っているセキュリティソフトのアイコンです。
環境
Windows11
React18
TypeScript4.7
MUI5.1
React-hook-formのインストール方法
// npm を使う場合 npm install react-hook-form // yarn を使う場合 yarn install react-hook-form
コード
まずは完成形のコードを貼っていきます。
フォームの使いまわしができるように
コンポーネントに分けました。
親コンポーネント:
import { memo } from 'react'; // React-hook-form import { useForm, SubmitHandler } from 'react-hook-form'; // MUI import { Avatar, CssBaseline, Box, Typography, Container, Button, } from '@mui/material'; import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; // Components import Input from '../Atoms/FormInputText'; // Input form type export type FormValues = { email: string; password: string; }; const LoginForm = memo(() => { const { handleSubmit, control } = useForm<FormValues>(); // Validation rules for login form const validationRules = { email: { required: 'Email is required', pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, message: 'Please enter a valid email', }, maxLength: { value: 254, message: 'Email is too long' }, }, password: { required: 'Password is required', minLength: { value: 6, message: 'Password is too short' }, }, }; const onSubmit: SubmitHandler<FormValues> = (data: FormValues) => { console.log(`data: ${JSON.stringify(data)}`); }; return ( <Container component='main' maxWidth='xs'> <CssBaseline /> <Box sx={{ marginTop: 8, display: 'flex', flexDirection: 'column', alignItems: 'center', }} > <Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}> <LockOutlinedIcon /> </Avatar> <Typography component='h1' variant='h5'> Log in </Typography> <Box component='form' noValidate sx={{ mt: 1 }}> {/* Input for email */} <Input name={'email'} rules={validationRules.email} required={true} id={'email'} type={'email'} label={'Email Address'} autoComplete={'email'} autoFocus={true} control={control} /> {/* Input for password */} <Input name={'password'} rules={validationRules.password} required={true} id={'password'} type={'password'} label={'Password'} autoComplete={'password'} autoFocus={true} control={control} /> {/* Submit button */} <Button onClick={handleSubmit(onSubmit)} fullWidth variant='contained' sx={{ mt: 3, mb: 2 }} > LOG IN </Button> </Box> </Box> </Container> ); }); export default LoginForm;
子コンポーネント:
import { FC, memo } from 'react'; import { Controller } from 'react-hook-form'; // MUI import TextField from '@material-ui/core/TextField'; // Form type import { FormInputProps } from '../Types/Form/FormInputProps'; const FormInputTmp: FC<FormInputProps> = memo((props) => { const { name, control, rules, required, type, id, label, autoComplete, autoFocus, } = props; return ( <Controller name={name} control={control} defaultValue={''} rules={rules} render={({ field: { onChange, value }, fieldState: { error }, formState, }) => ( <TextField margin='normal' fullWidth variant='outlined' required={required} type={type} id={id} label={label} name={name} autoComplete={autoComplete} autoFocus={autoFocus} error={!!error} onChange={onChange} value={value} helperText={error ? error.message : null} /> )} /> ); }); export default FormInputTmp;
型宣言したファイル:
any警察に捕まりませんように@@
type InputFiledType = 'text' | 'password' | 'email'; export interface FormInputProps { name: string; control: any; label: string; setValue?: any; required?: boolean; type?: InputFiledType; id?: string; autoComplete?: string; autoFocus?: boolean; error?: boolean; helperText?: string | undefined; rules: any; }
親コンポーネントの説明
まず、react-hook-formからuseFormとSubmitHandlerをインポートします。
import { useForm, SubmitHandler } from 'react-hook-form';
useFormは「インプット、セレクト要素のデータをreact-hook-formに登録」したり、
「入力されたデータをリセット」したりしてくれる
便利なメソッドが入ったオブジェクトを返します。
メソッドの種類はこちらから:
useForm | React Hook Form - Simple React forms validation
SubmitHandlerはTypeScript で書く場合に必要な型定義です。
submit イベントに関連して実行する関数の型宣言に使います。
次に、入力値の型宣言をします。
export type FormValues = { email: string; password: string; };
次に、useFromから、handleSubmit, controlを定義します。
useForm フックの直後に、フォームの入力値についての型を宣言します。
const { handleSubmit, control } = useForm<FormValues>();
handleSubmitは、
ログインボタンを押した時、フォーム データを受け取るためのもの、
controlにはコンポーネントを
React Hook Formに登録するためのメソッドが入っています。
次に、バリデーションのルールを定義します。
今回はemailには入力必須、emailの形式で、254文字までというルール、
passwordには入力必須、6文字以上というルールをつけています。
// Validation rules for login form const validationRules = { email: { required: 'Email is required', pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, message: 'Please enter a valid email', }, maxLength: { value: 254, message: 'Email is too long' }, }, password: { required: 'Password is required', minLength: { value: 6, message: 'Password is too short' }, }, };
利用できるバリデーションルールは以下です。
- required
- min
- max
- minLength
- maxLength
- pattern
- validate
validateを使うとカスタムルールを設定することができます。
次に、子コンポーネント(フォーム)に必要な情報を渡します。
{/* Input for email */} <Input name={'email'} rules={validationRules.email} required={true} id={'email'} type={'email'} label={'Email Address'} autoComplete={'email'} autoFocus={true} control={control} /> {/* Input for password */} <Input name={'password'} rules={validationRules.password} required={true} id={'password'} type={'password'} label={'Password'} autoComplete={'password'} autoFocus={true} control={control} /> {/* Submit button */} <Button onClick={handleSubmit(onSubmit)} fullWidth variant='contained' sx={{ mt: 3, mb: 2 }} > LOG IN </Button>
ボタンをクリックすると、handleSubmit()が実行され
バリデーションokだったら自分で定義したonSubmit関数が呼ばれます。
handleSubmit は、第 1引数の関数を「バリデーションokだった場合」に実行します。
「バリデーション🙅♀️」だった場合、第2引数にダメだった時の関数を渡すことができるみたいです。
呼ばれる関数は以下です。
SubmitHandlerに続けて、フォーム入力値の型を宣言しています。
引数のdataには、フォーム入力値のデータがオブジェクトとして格納されています。便利。
const onSubmit: SubmitHandler<FormValues> = (data: FormValues) => { console.log(`data: ${JSON.stringify(data)}`); };
子コンポーネント
Controllerをreact-hook-formからインポートします。
import { Controller } from 'react-hook-form';
ControllerはMUIなどの為のラッパーコンポーネントです。
これで要素をラップしてあげることで要素を制御できます。
ちなみに、reactt-hook-formで要素を制御するには2つの方法があって、
ControllerとRegisterというものがあります。
Controllerを使うとフォーム入力時に再レンダリングするので
基本的にはregisterの使用がおすすめされています。
ただ、MUIを使用する場合すでにMUIによって要素が制御されてるので
react-hook-formのControllerを使うことになります。
Controllerに関するDoc:
Controller | React Hook Form - Simple React forms validation
コードの説明に戻ります。
親コンポーネントから必要な情報をpropsで受け取って、
それぞれのpropを渡しています。
<Controller name={name} control={control} defaultValue={''} rules={rules} render={({ field: { onChange, value }, fieldState: { error } }) => ( <TextField margin='normal' fullWidth variant='outlined' required={required} type={type} id={id} label={label} name={name} autoComplete={autoComplete} autoFocus={autoFocus} error={!!error} onChange={onChange} value={value} helperText={error ? error.message : null} /> )}
nameは必須です。これがないと判別してくれません。
rules={rules}は、親コンポーネントで定義したバリデーションルール
control={control}react-hook-formにコンポーネントを登録するメソッド
defaultValue={''}で、初期レンダーの際に呼ばれる初期値を設定できます。
render={}
これはイベントと値を
この関数は、field、fieldState、formStateの3つを持っています。
fieldStateのerrorにはバリデーションがダメだった時のメッセージが格納されます。
バリデーションがダメだった時は
helperTextにエラーメッセージを入れることでエラーを表示しています。
error={!!error} helperText={error ? error.message : null}
コードの説明は以上となります。
お付き合いいただきありがとうございました!