Closed6
(更新回数③最終更新5.24)【Material-ui,React-Hook-form】useFieldArrayを用いた動的フォーム作成方法
何について書く?
- 動的フォーム作成方法
- 陥った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が作られるだけで、配列を送信出来ないフォームとなってしまうため却下した。単純にコンポーネントを複製したい時に有効そうだ。
参考サイト
更新① 何について書く?
- 動的フォームが複数ある時の記載方法
内容
- 実装方法
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の理解を今後も深め、更新続ける。
参考サイト
更新②何について書く?
- ページがレンダリングされたタイミングで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の制御を行う。
更新③ 何について書く?
- 編集画面で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
参考サイト
最後バグりますよ
コメントありがとうございます。
後ほど確認の上修正しておきます。とても励みになります。
このスクラップは2022/04/17にクローズされました