reactjs - 在 react-hook-forms 表单中动态添加或删除项目并注册输入?
问题描述
我正在尝试实现一个相当复杂的表单,该表单具有日期选择器和输入,用户可以添加多个(或删除)。然后,该数据会在提交时添加到整个表单中。我如何获得 react-hook-forms 以在真实表单中注册那个小的动态人造表单?
这是人造表单输入:
<AddPackagesStyle>
<Label htmlFor="addedPackages" label="Add Packages" />
<DatePicker
id="dateRange"
selected={startDate}
selectsRange
startDate={startDate}
endDate={endDate}
placeholderText="select dates"
onChange={onDateChange}
/>
<PackageInput
id="PackageSelect"
placeholder="Select Package"
type="text"
value={name}
// @ts-ignore
onChange={(e) =>
// @ts-ignore
setName(e.target.value)
}
/>
<ButtonContainer>
<button type="button" onClick={clearAll}>
Clear
</button>
<button
type="button"
// @ts-ignore
onClick={addPackage}
>
Add
</button>
</ButtonContainer>
</AddPackagesStyle>
这些条目被添加到 useState 挂钩中的数组中:
const [addedPackages, setAddedPackages] = useState<any[]>([])
然后这在 JSX 中呈现为添加包:
<ContentSubscriptionWrapper>
{addedPackages.length !== 0 &&
addedPackages.map((addedPackage, idx) => (
// @ts-ignore
<>
<ContentSubscriptionColumn>
{addedPackage.name && addedPackage.name}
</ContentSubscriptionColumn>
<ContentSubscriptionColumn>
{addedPackage.startDate &&
addedPackage.startDate.toString()}
</ContentSubscriptionColumn>
<ContentSubscriptionColumn>
{addedPackage.endDate && addedPackage.endDate.toString()}
</ContentSubscriptionColumn>
<button type="button" onClick={() => removePackage(idx)}>
X
</button>
</>
))}
</ContentSubscriptionWrapper>
因此,在提交表单之前,必须设置“添加包”。我在哪里添加 {...register} 对象以添加到较大的表单对象以进行提交?
const {
control,
register,
handleSubmit,
formState: { errors },
} = useForm<any>()
const onSubmit = (data: any) => {
console.log(data)
}
解决方案
我创建了一个 CodeSandbox 试图重现您的用例并使用 Material UI 快速完成它,但您应该明白这个想法并可以使用您自己的组件对其进行修改。
- 您应该让 RHF 处理表单的所有状态
- 使用 RHF来管理(添加、删除)你的包/订阅——这里
useFieldArray
不需要使用watch
useForm
为您的组件使用单独的<AddPackage />
,这样做的好处是您将对此子表单进行表单验证(如果它应该要求所有<AddPackage />
需要的字段) - 我在演示中添加了验证来证明这一点
添加包.tsx
export const AddSubscription: React.FC<AddSubscriptionProps> = ({ onAdd }) => {
const {
control,
reset,
handleSubmit,
formState: { errors }
} = useForm<Subscription>({
defaultValues: { from: null, to: null, package: null }
});
const clear = () => reset();
const add = handleSubmit((subscription: Subscription) => {
onAdd(subscription);
clear();
});
return (
<Card variant="outlined">
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Grid container spacing={1} p={2}>
<Grid item container spacing={1} xs={12}>
<Grid item xs={6}>
<Controller
name="from"
control={control}
rules={{ required: "Required" }}
render={({ field }) => (
<DatePicker
{...field}
label="From"
renderInput={(params) => (
<TextField
{...params}
fullWidth
error={!!errors.from}
helperText={errors.from?.message}
/>
)}
/>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
name="to"
control={control}
rules={{ required: "Required" }}
render={({ field }) => (
<DatePicker
{...field}
label="To"
renderInput={(params) => (
<TextField
{...params}
fullWidth
error={!!errors.to}
helperText={errors.to?.message}
/>
)}
/>
)}
/>
</Grid>
</Grid>
<Grid item xs={12}>
<Controller
name="package"
control={control}
rules={{ required: "Required" }}
render={({ field: { onChange, ...field } }) => (
<Autocomplete
{...field}
options={packages}
onChange={(e, v) => onChange(v)}
renderInput={(params) => (
<TextField
{...params}
label="Package"
fullWidth
error={!!errors.package}
helperText={errors.package && "Required"}
/>
)}
/>
)}
/>
</Grid>
<Grid item xs={12}>
<Stack spacing={1} direction="row" justifyContent="end">
<Button variant="outlined" onClick={clear}>
Clear
</Button>
<Button variant="contained" onClick={add} type="submit">
Add
</Button>
</Stack>
</Grid>
</Grid>
</LocalizationProvider>
</Card>
);
};
表格.tsx
export default function Form() {
const { control, handleSubmit } = useForm<FormValues>({
defaultValues: {
seats: "",
addOns: false
}
});
const { fields, append, remove } = useFieldArray({
control,
name: "subscriptions"
});
const onSubmit = (data) => console.log("data", data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container spacing={2}>
<Grid item xs={12}>
<Box display="flex" justifyContent="end" gap={1}>
<Button variant="outlined">Cancel</Button>
<Button variant="contained" type="submit">
Save
</Button>
</Box>
</Grid>
<Grid item xs={12}>
<Controller
name="seats"
control={control}
render={({ field }) => (
<TextField {...field} fullWidth label="Seats" />
)}
/>
</Grid>
<Grid item xs={12}>
<AddSubscription onAdd={append} />
</Grid>
<Grid item xs={12}>
<List>
{fields.map((field, index) => (
<ListItem
key={field.id}
secondaryAction={
<IconButton
edge="end"
aria-label="delete"
onClick={() => remove(index)}
>
<DeleteIcon />
</IconButton>
}
>
<ListItemText
primary={field.package.label}
secondary={
<span>
{formatDate(field.from)} - {formatDate(field.to)}
</span>
}
/>
</ListItem>
))}
</List>
</Grid>
<Grid item xs={12}>
<Controller
name="addOns"
control={control}
render={({ field: { value, onChange } }) => (
<FormControlLabel
control={<Checkbox checked={!!value} onChange={onChange} />}
label="Add-ons"
/>
)}
/>
</Grid>
</Grid>
</form>
);
}
推荐阅读
- r - R:在函数(x,y)中找不到对象y [通过r中的数据帧的函数]
- mysql - In mysql how can I get only rows from one table which do not link to any rows in another table with a specific ID
- asp.net-mvc-4 - 使用 ajax 调用操作后表单数据未更新
- ubuntu - 我很难在我的 ubuntu virtualbox 上更新我的 gcc,因为我当前的 gcc 不允许我正确运行代码
- webpack - loaderUtil.getOptions 返回 null:“this”对象在 Webpack 4 的加载器函数中为空
- makefile - $和$$之间make的功能和区别
- node.js - 使用 Nginx、node-http-proxy 屏蔽 IP 地址
- c# - C# 控制台应用程序:Visual Studio 找不到配置文件
- c++ - 使用 libcurl 和 C++ 将图片上传到受密码保护的目录
- chart.js - Chartjs:删除特定标签