在 react-hook-forms 表单中动态添加或删除项目并注册输入?

Dynamically adding or removing items within a react-hook-forms form and registering the inputs?

我正在尝试实现一个相当复杂的表单,其中包含日期选择器和输入,用户可以添加多个(或删除)。然后,该数据会在提交时添加到整个表单中。我如何获得 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>

因此在提交表单之前,必须设置 'add packages'。在哪里添加 {...register} 对象以添加到较大的表单对象以供提交?

const {
    control,
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<any>()
  const onSubmit = (data: any) => {
    console.log(data)
  }

我创建了一个 CodeSandbox 试图重现您的用例并使用 Material UI 来快速完成它,但您应该了解并可以使用您自己的组件对其进行修改。

  • 你应该让 RHF 处理你表单的所有状态
  • 使用 RHF 的 useFieldArray 来管理(添加、删除)您的 packages/subscriptions - 无需在此处使用 watch
  • 为您的 <AddPackage /> 组件使用单独的 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>
  );
};

Form.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>
  );
}