MUSIv5+React Hook Formv7에서 자주 사용하는 구성 요소 통합

며칠 전에Vite+React+TypeScript+Emotion+MUI v5에서 입력 형식 라이브러리인 React Hook Form을 가져왔습니다.
React + MUI v5의 입력 형식에 사용되는 프로그램 라이브러리는 React Hook Form의 선택입니다
이번에 우리는 MUSIv5 이하에서 자주 사용하는 구성 요소와 React Hook Form의 합작을 조사했다.
  • TextField
  • Select
  • RadioGroup
  • DatePicker
  • 다중 체크박스
  • 일반적으로validation 라이브러리를 사용하지만 이번에는validation 라이브러리를 가져오지 않은 상태에서React Hook Form의validate만 사용합니다.
    validation 프로그램 라이브러리를 선택하기 전에 MUI v5와 React Hook Form의 합작 방법을 확인하기 위해서입니다.

    합작적 방법


    React의 어셈블리에는 제어 어셈블리와 Uncontroled component(비제어 어셈블리)가 있습니다.
    ■ controlled component (제어된 구성 요소)
    controlled component (제어된 구성 요소) 는state로 입력값 등을 관리하는 구성 요소입니다.
    Controlled component의 샘플입니다.
    React.useState를 통해 input 요소의 입력 값을 관리합니다.
    import React from 'react'
    
    export function ControlledComponent() {
    +  const [inputValue, setInputValue] = React.useState<string>('')
    
      const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    +    setInputValue(event.target.value)
      }
    
      const handleSubmit = (event: React.MouseEvent<HTMLButtonElement>) => {
        event.preventDefault()
    +    console.log(inputValue)
      }
    
      return (
        <div>
    +      <input type="text" onChange={handleInputChange} value={inputValue} />
          <button type="submit" onClick={handleSubmit}>Submit</button>
        </div>
      )
    }
    
    ■ Uncontroled component(비제어 어셈블리)
    DOM 관리를 통해 값을 입력합니다.
    Uncontroled component의 샘플입니다.
    React.createRef에서 만든 Ref를 input 요소의 ref 속성으로 지정합니다.
    input 요소의 값은 Ref의current 속성을 통해 얻을 수 있습니다.
    import React from 'react'
    
    export function UncontrolledComponent() {
    +  const inputRef = React.createRef<HTMLInputElement>()
    
      const handleSubmit = (event: React.MouseEvent<HTMLButtonElement>) => {
        event.preventDefault()
    +    console.log(inputRef.current?.value)
      }
    
      return (
        <div>
    +      <input ref={inputRef} type="text" />
          <button type="submit" onClick={handleSubmit}>Submit</button>
        </div>
      )
    }
    
    ■ MUI 구성 요소와의 공동 작업
    MUI의 구성 요소는 제어 구성 요소입니다.
    React Hook Form에서는 제어된 구성 요소를 간단하게 처리하기 위해 Controller 구성 요소를 준비합니다.
    이 Controller 구성 요소를 사용하여 MCI 구성 요소를 처리합니다.

    TextField와의 협력


    먼저 MUI의 Text Field가 사용됩니다.
    React Hook Form에서 Text Field에 대한 초기 값 설정, 값 검증, Submit에서 값을 가져옵니다.
    우선 실행 결과다.
    이름 및 4자 미만을 입력하지 않은 경우 오류를 입력합니다.

    샘플 코드.
    Controller를 사용하지 않는 방법도 있지만 MUI의 Text Field 이외의 구성 요소는 대체적으로 Controller를 사용하기 때문에 Text Field도 특별히 Controller를 사용하여 실현했다.
  • 입력 값의 정의를 만듭니다.
  • useForm에서 필요한 함수를 가져와 기본값을 지정합니다.
  • 검증 규칙을 지정합니다.
  • 제출을 만들 때의 처리입니다.검증에 성공하면 입력 값은 파라미터를 통해 전달됩니다.
  • form 요소의 onSubmit에서 1.에서 가져온handleSubmit
  • Controller 구성 요소를 통해 Text Field와 ReactHookForm을 연결합니다.
  • name = "name"...1 또는 2에 지정된 고유한 이름을 지정합니다.
  • control={control}···지정1에서 획득한 control.
  • rules={validationRules.name}...2에 지정된 인증 규칙을 지정합니다.
  • render//ReactHookForm으로 제어되는 구성 요소를 지정합니다.
  • error={errors.name: 오류 33:=undefined}///지정에 오류가 있는지 없는지.
  • helperText={errors.name.
  • InputReactHookFormTextField
    import { Stack, TextField, Button } from '@mui/material'
    import { useForm, SubmitHandler, Controller } from 'react-hook-form'
    
    // 1. 入力値の定義を作成します。
    + type Inputs = {
    +  name: string
    + }
    
    export function InputReactHookFormTextField() {
      // 2. useFormで必要な関数を取得し、デフォルト値を指定します。
    +  const {
    +    control,
    +    handleSubmit,
    +  } = useForm<Inputs>({
    +    defaultValues: { name: 'longbridgeyuk' }
    +  })
    
      // 3. 検証ルールを指定します。
    +  const validationRules = {
    +    name: {
    +      required: '名前を入力してください。',
    +      minLength: { value: 4, message: '4文字以上で入力してください。' }
    +    }
    +  }
      
      // 4. サブミット時の処理を作成します。
      // 検証が成功すると呼び出され、引数で入力値が渡ってきます。
    +  const onSubmit: SubmitHandler<Inputs> = (data: Inputs) => {
    +    console.log(`submit: ${data.name}`)
    +  }
    
      return (
       {/* 5. form要素のonSubmitに1.で取得しているhandleSubmitを指定します */}
        <Stack component="form" noValidate 
    +    onSubmit={handleSubmit(onSubmit)} 
        spacing={2} sx={{ m: 2, width: '25ch' }}>
    
           {/* 6.Controllerコンポーネントで TextFieldをReactHookFormと紐づけます。*/}
    +      <Controller
    +        name="name"
    +        control={control}
    +        rules={validationRules.name}
    +        render={({ field, fieldState }) => (
              <TextField
    +            {...field}
                type="text"
                label="名前"
    +            error={fieldState.invalid}  
    +            helperText={fieldState.error?.message}
              />
            )}
          />
          <Button variant="contained" type="submit" >
            送信する
          </Button>
        </Stack>
      )
    }
    

    Select와의 협력


    다음은 Select 입니다.
    먼저 실행 이미지부터 확인합니다.

    기본적으로 Text Field와 같지만 MUI의 Select 자체는 여러 개의 구성 요소로 구성되어 있어 복잡도가 상응하여 증가한다.
    InputLabel, Select, FormHelperText가 포함된 FormControl 구성 요소
    FormControl의 error에 유효성 검사 오류가 있는지 여부를 지정합니다.
    유효성 검사 오류가 FormHelperText에 표시됩니다.
    Controller 구성 요소의 render 에 FormControl 이 지정되어 있습니다.
    import { Stack, FormControl, InputLabel, Select, MenuItem, FormHelperText, Button } from '@mui/material'
    import { useForm, SubmitHandler, Controller } from 'react-hook-form'
    
    // 1. 入力値の定義を作成します。
    + type Inputs = {
    +   area: number | ''
    + }
    
    // 2. useFormで必要な関数を取得し、デフォルト値を指定します。
    export function InputReactHookFormSelect() {
    +   const {
    +     control,
    +     handleSubmit,
    +   } = useForm<Inputs>({
    +     defaultValues: { area: 6 }
    +   })
    
     // 3. 検証ルールを指定します。
    +   const validationRules = {
    +     area: {
    +       validate: (value:number | '') => value !== '' || 'いずれかを選択してください。'
    +     }
    +   }
    
      // 4. サブミット時の処理を作成します。
    +   const onSubmit: SubmitHandler<Inputs> = (data: Inputs) => {
    +     console.log(`submit: ${data.area}`)
    +   }
    
      return (
       {/* 5. form要素のonSubmitに1.で取得しているhandleSubmitを指定します */}
        <Stack component="form" noValidate 
    +      onSubmit={handleSubmit(onSubmit)} 
          spacing={2} 
          sx={{ m: 2, width: '25ch' }}>
    
           {/* 6.Controllerコンポーネントで TextFieldをReactHookFormと紐づけます。*/}
    +      <Controller
    +        name="area"
    +        control={control}
    +        rules={validationRules.area}
    +        render={({ field, fieldState }) => (
    +          <FormControl fullWidth error={fieldState.invalid}>
                <InputLabel id="area-label">地域</InputLabel>
                <Select
                  labelId="area-label"
                  label="地域" // フォーカスを外した時のラベルの部分これを指定しないとラベルとコントロール線が被る
    +              {...field}
                >
                  <MenuItem value='' sx={{color:'gray'}}>未選択</MenuItem>
                  <MenuItem value={1}>北海道</MenuItem>
                  <MenuItem value={2}>東北</MenuItem>
                  <MenuItem value={4}>関東</MenuItem>
                  <MenuItem value={5}>中部</MenuItem>
                  <MenuItem value={6}>近畿</MenuItem>
                  <MenuItem value={7}>中国</MenuItem>
                  <MenuItem value={8}>四国</MenuItem>
                  <MenuItem value={9}>九州沖縄</MenuItem>
                </Select>
    +            <FormHelperText>{fieldState.error?.message}</FormHelperText>
              </FormControl>
            )}
          />
          <Button variant="contained" type="submit" >
            送信する
          </Button>
        </Stack>
      )
    }
    
    

    Radio Group과의 협력


    먼저 이미지를 실행합니다.

    코드는 Select와 마찬가지로 FormLabel, RadioGroup, FormControlLabel을 포함하는 FormControl 구성 요소가 있고 FormControl의 error에서 검증 오류가 있는지 여부를 지정합니다.
    유효성 검사 오류가 FormHelperText에 표시됩니다.
    Controller 구성 요소의 render 에 FormControl 이 지정되어 있습니다.
    import {
      Stack,
      RadioGroup,
      FormLabel,
      FormControlLabel,
      Radio,
      FormControl,
      Button,
      FormHelperText
    } from '@mui/material'
    import { useForm, SubmitHandler, Controller } from 'react-hook-form'
    
    // 1. 入力値の定義を作成します。
    + type Inputs = {
    +   gender: number
    + }
    
    export function InputReactHookFormRadioGroup() {
      
      // 2. useFormで必要な関数を取得し、デフォルト値を指定します。
    +  const {
    +    control,
    +    handleSubmit
    +  } = useForm<Inputs>({
    +    defaultValues: { gender: -1 }
    +  })
    
      // 3. 検証ルールを指定します。
    +  const validationRules = {
    +    gender: {
    +      validate: (value: number) => value !== -1 || 'いずれかを選択してください。'
    +    }
    +  }
    
      // 4. サブミット時の処理を作成します。
    +  const onSubmit: SubmitHandler<Inputs> = (data: Inputs) => {
    +    console.log(`submit: ${data.gender}`)
    +  }
    
      return (
        <Stack component="form" noValidate onSubmit={handleSubmit(onSubmit)} spacing={2} sx={{ m: 2, width: '25ch' }}>
          {/* 6.Controllerコンポーネントで TextFieldをReactHookFormと紐づけます。 */}
    +      <Controller
    +        name="gender"
    +        control={control}
    +        rules={validationRules.gender}
    +        render={({ field, fieldState }) => (
    +          <FormControl error={fieldState.invalid}>
                <FormLabel id="radio-buttons-group-label">Gender</FormLabel>
                <RadioGroup 
                  aria-labelledby="radio-buttons-group-label" 
    +              value={field.value} name="gender">
    +              <FormControlLabel {...field} value={1} control={<Radio />} label="男性" />
    +              <FormControlLabel {...field} value={2} control={<Radio />} label="女性" />
    +              <FormControlLabel {...field} value={0} control={<Radio />} label="未回答" />
                </RadioGroup>
    +            <FormHelperText>{fieldState.error?.message}</FormHelperText>
              </FormControl>
            )}
          />
    
          <Button variant="contained" type="submit">
            送信する
          </Button>
        </Stack>
      )
    }
    
    

    DatePicker와의 협력


    MUI의 DatePicker는 다음 링크에서 사용 방법과 일본어를 기사로 사용합니다.
    MUI v5 DatePicker 사용 방법
    MUI v5 DatePicker 사용법 2~일본어화~
    그때 만들어진 데이지 피커와 리액트 훅 포름이 연합했다.

    DatePicker는 개발도상국의 부품이기 때문에 문제가 많고 다른 부품의 동작과 미묘하게 다르다.
    Select와 마찬가지로 FormControl을 사용하여 검증 오류를 표시하려고 했지만 입력 상자의 테두리와 탭에 오류가 표시되지 않았습니다. 빨간색으로 표시됩니다.
    오류를 검증할 때 Text Field의 error를 표시하고 오류를 검증하는 메시지는 Text Field의 helperText가 지정합니다.
    import { Stack, TextField, Button, FormControl } from '@mui/material'
    import { useForm, SubmitHandler, Controller } from 'react-hook-form'
    import { LocalizationProvider, DatePicker } from '@mui/lab'
    import AdapterDateFns from '@mui/lab/AdapterDateFns'
    import ja from 'date-fns/locale/ja'
    
    // 1. 入力値の定義を作成します。
    + type Inputs = {
    +  applicationDate: Date | null
    + }
    
    export function InputDateTimePicker() {
    // 2. useFormで必要な関数を取得し、デフォルト値を指定します。
    +  const {
    +    control,
    +    handleSubmit
    +  } = useForm<Inputs>({
    +    defaultValues: { applicationDate: new Date() }
    +  })
    
    // 3. 検証ルールを指定します。
    +  const validationRules = {
    +    applicationDate: {
    +      validate: (val: Date | null) => {
    +        if (val == null ) {
    +          return '申請日を入力してください。'
    +        }
    +        if (Number.isNaN(val.getTime())) {
    +          return '日付を正しく入力してください。'
    +        }
    +        return true
    +      },
    +    }
    +  }
    
    // 4. サブミット時の処理を作成します。
    +  const onSubmit: SubmitHandler<Inputs> = (data: Inputs) => {
    +    console.log(`submit: ${data.applicationDate}`)
    +  }
    
      return (
        <LocalizationProvider dateAdapter={AdapterDateFns} locale={ja}>
         {/* 5. form要素のonSubmitに1.で取得しているhandleSubmitを指定します */}
          <Stack component="form" noValidate 
    +	 onSubmit={handleSubmit(onSubmit)} 
    	 spacing={2} sx={{ m: 2, width: '25ch' }}>
    	 
    	{/* 6.Controllerコンポーネントで TextFieldをReactHookFormと紐づけます。*/}
    +        <Controller
    +          name="applicationDate"
    +          control={control}
    +          rules={validationRules.applicationDate}
    +          render={({ field, fieldState }) => (
                <DatePicker
                  label="申請日"
                  inputFormat="yyyy年MM月dd日"
                  mask="____年__月__日"
                  leftArrowButtonText="前月を表示"
                  rightArrowButtonText="次月を表示"
                  toolbarTitle="日付選択"
                  cancelText="キャンセル"
                  okText="選択"
                  toolbarFormat="yyyy年MM月dd日"
                  renderInput={(params) => (
                    <TextField
                      {...params}
    +                  error={fieldState.invalid}
    +                  helperText={fieldState.error?.message}
                    />
                  )}
                  PaperProps={{ sx: styles.paperprops }}
                  DialogProps={{ sx: styles.mobiledialogprops }}
    +              {...field}
                />
              )}
            />
    
            <Button variant="contained" type="submit">
              送信する
            </Button>
          </Stack>
        </LocalizationProvider>
      )
    }
    
    const styles = {
      paperprops: {
        'div[role=presentation]': {
          display: 'flex',
          '& .PrivatePickersFadeTransitionGroup-root:first-of-type': {
            order: 2
          },
          '& .PrivatePickersFadeTransitionGroup-root:nth-of-type(2)': {
            order: 1,
            '& div::after': {
              content: '"年"'
            }
          },
          '& .MuiButtonBase-root': {
            order: 3
          }
        }
      },
      mobiledialogprops: {
        '.PrivatePickersToolbar-dateTitleContainer .MuiTypography-root': {
          fontSize: '1.5rem'
        },
        'div[role=presentation]:first-of-type': {
          display: 'flex',
          '& .PrivatePickersFadeTransitionGroup-root:first-of-type': {
            order: 2
          },
          '& .PrivatePickersFadeTransitionGroup-root:nth-of-type(2)': {
            order: 1,
            '& > div::after': {
              content: '"年"'
            }
          },
          '& .MuiButtonBase-root': {
            order: 3
          }
        }
      }
    }
    
    

    여러 CheckBox(CheckBox Group식)와 협력


    먼저 이미지를 실행합니다.
    체크박스가 2개 이상 체크되지 않으면 오류가 발생합니다.

    CheckBox는 라디오 그룹처럼 여러 개의 CheckBox를 처리하는 구성 요소가 없습니다.
    여러 체크박스와 리액트 훅 포름을 좋은 느낌으로 시원시원하게 협업할 방법은 마련되지 않은 것 같다.
    따라서 그다지 시간이 걸리지 않는 합작 방법으로
    각 확인란은 React Hook Form과 협업하며 입력 값의 정의에 검증 결과만 설정하는 항목checkerr를 설정합니다.
    검사 규칙에서 각 복선상자의 검증은 오류가 없습니다. 검증 결과는 checkerr로 설정됩니다.
    오류 없음FormControl로 설정된 error, 오류 내용은FormHelperText에 표시됩니다.
    import { Stack, Button, FormControlLabel, Checkbox, FormControl, FormGroup, FormHelperText } from '@mui/material'
    import { useForm, SubmitHandler, Controller } from 'react-hook-form'
    
    // 1. 入力値の定義を作成します。
    // 個々のチェックボックス用の checks と
    // 検証エラー用に checkerr を用意しています。
    + type Inputs = {
    +   checks: boolean[]
    +   checkerr: boolean
    + }
    
    export function InputReactHookCheckBox() {
    
    // 2. useFormで必要な関数を取得し、デフォルト値を指定します。
    +   const {
    +     control,
    +     handleSubmit,
    +     formState: { errors },
    +     getValues,
    +     clearErrors,
    +     setError
    +   } = useForm<Inputs>({
    +     defaultValues: {
    +       checks: [false, true, false],
    +       checkerr: false
    +     }
    +   })
    
    // 3. 検証ルールを指定します。
    // 個々のチェックボックスが検証エラーにならないようにし
    // 検証結果は checkerr に設定するようにしています。
    +   const validationRules = {
    +     checks: {
    +       validate: () => {
    +         clearErrors(`checkerr`)
    +         const checks = [getValues(`checks.${0}`), getValues(`checks.${1}`), getValues(`checks.${2}`)]
    +         if (checks.filter((v) => v === true).length < 2) {
    +           setError(`checkerr`, { message: 'いずれか2つ選択してください。' })
    +         }
    +         return true
    +       }
    +     }
    +   }
    
    // 4. サブミット時の処理を作成します。検証が成功すると呼び出され、引数で入力値が渡ってきます。
    +   const onSubmit: SubmitHandler<Inputs> = (data: Inputs) => {
    +     console.log(`submit: checks[0]=${data.checks[0]} checks[1]=${data.checks[1]}  checks[2]=${data.checks[2]}`)
    +   }
    
      return (
        <Stack component="form" noValidate onSubmit={handleSubmit(onSubmit)} spacing={2} sx={{ m: 2, width: '25ch' }}>
        
    +      <FormControl fullWidth error={errors.checkerr !== undefined}>
            <span>いずれか2つ選択。</span>
            <FormGroup>
    	
    +           <Controller
    +             name={`checks.${0}`}
    +             control={control}
    +             rules={validationRules.checks}
    +             render={({ field }) => (
    +               <FormControlLabel label="チェック 1" control={<Checkbox {...field} checked={field.value} />} />
    +             )}
    +           />
    
    +          <Controller
    +            name={`checks.${1}`}
    +            control={control}
    +            rules={validationRules.checks}
    +            render={({ field }) => (
    +              <FormControlLabel label="チェック 2" control={<Checkbox {...field} checked={field.value} />} />
    +            )}
    +          />
    
    +         <Controller
    +           name={`checks.${2}`}
    +           control={control}
    +           rules={validationRules.checks}
    +           render={({ field }) => (
    +             <FormControlLabel label="チェック 3" control={<Checkbox {...field} checked={field.value} />} />
    +           )}
    +         />
     
            </FormGroup>
    +        <FormHelperText>{errors.checkerr?.message}</FormHelperText>
          </FormControl>
    
          <Button variant="contained" type="submit">
            送信する
          </Button>
        </Stack>
      )
    }
    

    총결산


    이번에 우리는 MUSIv5가 자주 사용하는 구성 요소와 React Hook Form의 합작을 조사했다.
    Text Field, Select, Radio Group에 대해 우리는 간단한 조합을 진행했고 DatePicker는 구성 요소 자체를 처리하는 데 많은 시간을 들였다.
    여러 개의 확인란에 대해 원래 어떻게 해야 정답인지 몰랐다.
    MUI v5 이전에는 여러 개의 체크 상자를 처리하는 방법이 있었던 것 같지만, MUI v5는 이전 방법으로는 사용할 수 없었기 때문에 보도와 같은 방법을 사용했다.
    대체로 동작 감각이 좋아서 좋아요.😉

    좋은 웹페이지 즐겨찾기