Closed6

(更新回数③最終更新5.24)【Material-ui,React-Hook-form】useFieldArrayを用いた動的フォーム作成方法

hirohiro

何について書く?

  • 動的フォーム作成方法
  • 陥ったNG手法も紹介

内容

  • まず成功例から紹介
import { Button, Grid, InputLabel, Radio, RadioGroup } from '@material-ui/core'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import { Add } from '@material-ui/icons'
import { Controller, useForm, useFieldArray } from 'react-hook-form'
//-------------------------------
  const { register, handleSubmit, errors, reset, setValue, control } = useForm()
  const { fields, append, remove } = useFieldArray({
    control,
    name: 'tests',
  })
//-------------------------------

const Test = () => {
  return(
    <form>
      {fields.map((field, index) => (
        <div key={field.id}>
          {//★★↑ココ重要 配列にしたい内容を一つのタグで囲いkeyにfield.idを指定する。これをしないと削除機能で不具合が起きる。}
          <Grid item md={4}>
            <InputLabel shrink htmlFor="age-native-label-placeholder">
              テストテキストボックス
            </InputLabel>
            <FormControl>
              <TextField
                name={`tests[${index}].test`}
                type="date"
                error={errors.tests ? Boolean(errors.tests[index]?.test) : null}
              />
            </FormControl>
            <Button size="medium" onClick={() => remove(index)} >
              <BackspaceIcon />
            </Button>
          </Grid>
          <Grid item md={4}>
            <Button size="medium" onClick={() => append({})} >
              <Add /> ボタンを押してテキストボックス追加
            </Button>
          </Grid>
        </div>
      ))}
     <Grid item md={12}>
        <Button className={classes.button} type="submit" variant="contained" color="primary">
          送信
        </Button>
     </Grid>
    </form>
  )
}

export default Test
  • useFieldArrayをreact-hook-formからインポートするだけで上記のようにボタンによるフォームの追加が可能となる。次に示すのはNG例。 ライブラリーを使用せず、単純に.fill.mapメソッドを使用して増やしてしまった手法。
{Array(5).fill(0).map(() => 
<FormCompornent />
)}

任意の個数をArray()の引数に与えることで、次のfillメソッドにて空の配列が作成され、最終的にmapメソッドにて増やしたい<FormCompornent>が任意の個数分レンダリングできる。
しかしこれでは同一nameとのformが作られるだけで、配列を送信出来ないフォームとなってしまうため却下した。単純にコンポーネントを複製したい時に有効そうだ。

参考サイト

hirohiro

更新① 何について書く?

  • 動的フォームが複数ある時の記載方法

内容

  • 実装方法
import { Button, Grid, InputLabel, Radio, RadioGroup } from '@material-ui/core'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import { Add } from '@material-ui/icons'
import { Controller, useForm, useFieldArray } from 'react-hook-form'
//-------------------------------------------------------------
const {
  fields: typeAsFields,
  append: typeAsAppend,
  remove: typeAsRemove,
} = useFieldArray({ control, name: 'typeAs' })
const {
  fields: typeBsFields,
  append: typeBsAppend,
  remove: typeBsRemove,
} = useFieldArray({ control, name: 'TypeBs' })
//-------------------------------------------------------------
const Test = () => {
  return(
    <form>
      {typeAsFields.map((field, index) => (
        <div key={field.id}>
          <Grid item md={4}>
            <InputLabel shrink htmlFor="age-native-label-placeholder">
              1つ目の動的フォーム
            </InputLabel>
            <FormControl>
              <TextField
                name={`typeAs[${index}].test`}
                type="date"
                error={errors.testAs ? Boolean(errors.testAs[index]?.test) : null}
              />
            </FormControl>
            <Button size="medium" onClick={() => typeAsRemove(index)} >
              <BackspaceIcon />
            </Button>
          </Grid>
          <Grid item md={4}>
            <Button size="medium" onClick={() => typeAsAppend({})} >
              <Add /> ボタンを押してテキストボックス追加
            </Button>
          </Grid>
        </div>
      ))}
      {typeBsFields.map((field, index) => (
        <div key={field.id}>
          <Grid item md={4}>
            <InputLabel shrink htmlFor="age-native-label-placeholder">
              2つ目の動的フォーム
            </InputLabel>
            <FormControl>
              <TextField
                name={`typeBs[${index}].test`}
                type="date"
                error={errors.testBs ? Boolean(errors.testBs[index]?.test) : null}
              />
            </FormControl>
            <Button size="medium" onClick={() => typeBsRemove(index)} >
              <BackspaceIcon />
            </Button>
          </Grid>
          <Grid item md={4}>
            <Button size="medium" onClick={() => typeBsAppend({})} >
              <Add /> ボタンを押してテキストボックス追加
            </Button>
          </Grid>
        </div>
      ))}
      <Grid item md={12}>
        <Button className={classes.button} type="submit" variant="contained" color="primary">
          送信
        </Button>
      </Grid>
    </form>
  )
}

export default Test

-動的なフォームを複数作成できることにより機能の幅が広がる。

  • useReactFormの理解を今後も深め、更新続ける。

参考サイト

https://codesandbox.io/s/react-hook-form-multiple-usefieldarray-in-a-form-ffboe?from-embed=&file=/src/index.js

hirohiro

更新②何について書く?

  • ページがレンダリングされたタイミングで1回Appendが作動し1つフォームが出ている状態にする。
  • はじめにレンダリングされるフォームにだけRemoveをつけない。

内容

  • 結論
const Form = () => {

  const { register, handleSubmit, errors, reset, control, watch, setValue } = useForm({
    mode: 'all',
  })

  const { fields, append, remove } = useFieldArray({
    control,
    name: 'test',
  })

  const handler = (formData) => {
    onSubmit(formData)
    reset()
  }

  useEffect(() => {
    append({}) //←ここによりはじめに1つレンダリングされる
  }, []
  )

  return (
    <form onSubmit={handleSubmit(handler)}>
    {fields.map((field, index) => (
              <Grid container key={field.id} alignItems='center' >
                <Grid item sm={6}>
                  <FormControl fullWidth>
                    <TextField
                      name={`tests[${index}].name`}
                      inputRef={register({ required: true })}
                      type="date"
                      variant="outlined"
                    />
                  </FormControl>
                </Grid>
                {index !== 0 &&  //←この記載によりひとつめのフォームにはremoveがつかない。
                  <Button onClick={() => remove(index)}>
                    <CloseIcon />
                  </Button>
                }
            ))}
              <Grid item sm={12}>
                <Button variant="outlined" fullWidth onClick={() => append({})}>
                  <Typography >テストを追加する</Typography>
                </Button>
              </Grid>
    </form>
  )
}

export default Form

このような方法で、useFieldArrayの制御を行う。

hirohiro

更新③ 何について書く?

  • 編集画面でuseFieldArrayの初期値を入れる方法

内容

  • 結論

const EditForm = ({ tests }: EditFormProps) => {
  const testsData = tests

  const testDataInsert = testsData.map((item) => ({
    id: item.id,
    name: item.name
  }))

  const { register, handleSubmit, errors, reset, control, setValue, formState, watch } = useForm({
    mode: 'onTouched',
    defaultValues: { testDataInsert }
  })

  const { fields, append, remove } = useFieldArray({
    control,
    name: 'testDataInsert',
  })

 
  const handler = (editStaff) => {
    onSubmit(editStaff)
  }


  return (
    <form onSubmit={handleSubmit(handler)}>
                  {fields.map((field, index) => (
                    <Grid container justify='center' key={field.id}>
                      <Grid item xs={6}>
                        <FormControl variant="outlined" size="small">
                          <Controller
                            name={`testDataInsert [${index}].name`}
                            control={control}
                            rules={{ required: true }}
                            defaultValue={’’}
                            as={
                              <Select>
                                {selectData.map((item, index) => <MenuItem key={index} value={item}>{item}</MenuItem>)}
                              </Select>
                            }
                          />
                        </FormControl>
                      </Grid>
                      <Grid item xs={1}>
                        <Button size="medium" onClick={() => {
                          remove(index)
                        }}>
                          <CloseIcon />
                        </Button>
                      </Grid>
                    </Grid>
                  ))}
                  <Grid item xs={12}>
                    <Button size="medium" onClick={() => append({})} >
                      <Add /> 追加
                    </Button>
                  </Grid>
                </Grid>
              </Grid>
            </Grid>
          }
        </Grid>
      </Paper>
      <Grid item md={12}>
        <Button type="submit" variant="contained" color="primary">
          保存する
        </Button>
      </Grid>
    </form>
  )
}

export default EditForm

参考サイト

https://shuxblog.com/react-hook-form-usefieldarray/

Hirotomo YamadaHirotomo Yamada

最後バグりますよ

hirohiro

コメントありがとうございます。
後ほど確認の上修正しておきます。とても励みになります。

このスクラップは2022/04/17にクローズされました