为什么我的表单数据值与提交的输入值不对应?

Why does my Form Data Value does not correspond to submitted Input Value?

我目前正在尝试创建一个动态 select/input 组件,您可以在其中从 select 选项中选择值或 通过 selecting“其他”select 选项在输入字段中键入您自己的值。

现在我被更新表单数据等同于 selected 选项/输入值的值所困扰。表单数据值始终保持初始/默认值。

App.js

...

export default function App() {
  const methods = useForm({});
  const { handleSubmit } = methods;

  const customSalutationOptions = [
    { title: "Not specified", value: "null" },
    { title: "Male", value: "male" },
    { title: "Female", value: "female" }
  ];

  const defaultValues = {
    salutation: "null"
  };

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <div className="App">
      <FormProvider {...methods}>
        <form onSubmit={handleSubmit(onSubmit)}>
          <SelectOrInput
            variant="outlined"
            name={`contactPerson[0].salutation`}
            defaultValue={defaultValues}
            selectOptions={customSalutationOptions}
          />
          <Button type="submit" color="primary" fullWidth variant="contained">
            Submit
          </Button>
        </form>
      </FormProvider>
    </div>
  );
}

components/SelectOrInput.tsx

...

type Props = {
  name: string;
  label: string;
  selectOptions: [{ title: string; value: string }];
  defaultValue: any;
  shouldUnregister: boolean;
  variant: "filled" | "outlined" | "standard";
};

export default function SelectOrInput({
  name,
  label,
  selectOptions,
  defaultValue,
  shouldUnregister,
  variant
}: Props) {
  const classes = useStyles();
  const { control } = useFormContext();
  const [showCustomInput, setShowCustomInput] = useState(false);
  const [value, setValue] = useState(selectOptions[0].value);

  const additionalInput = [{ title: "Other", value: "" }];

  const combindedOptions = selectOptions.concat(additionalInput);

  const handleInputSelectChange = (
    event: React.ChangeEvent<{ value: unknown }>
  ): void => {
    const value = event.target.value as string;
    if (value === "") {
      const newState = !showCustomInput;
      setShowCustomInput(newState);
      console.log(value);
      setValue(value);
    } else {
      setValue(value);
    }
  };

  const resetCustomInputToSelect = (event: React.MouseEvent<HTMLElement>) => {
    const newState = !showCustomInput;
    setValue(combindedOptions[0].value);
    setShowCustomInput(newState);
  };

  return (
    <>
      {showCustomInput ? (
        <FormControl className={classes.input}>
          <Controller
            name={name}
            control={control}
            shouldUnregister={shouldUnregister}
            render={({ field }) => (
              <TextField
                {...field}
                label={label}
                InputLabelProps={{ shrink: true }}
                variant={variant}
                placeholder="Other..."
                autoFocus
                type="text"
                onChange={handleInputSelectChange}
                value={value}
                InputProps={{
                  endAdornment: (
                    <InputAdornment position="end">
                      <IconButton
                        size="small"
                        onClick={resetCustomInputToSelect}
                        id="custominput-closebutton"
                      >
                        <CloseIcon fontSize="small" />
                      </IconButton>
                    </InputAdornment>
                  )
                }}
              ></TextField>
            )}
          />
        </FormControl>
      ) : (
        <FormControl className={classes.input} variant={variant}>
          <InputLabel id={`label-select-${label}`}>{label}</InputLabel>
          <Controller
            name={name}
            defaultValue={defaultValue}
            control={control}
            shouldUnregister={shouldUnregister}
            render={({ field }) => (
              <Select
                {...field}
                label={label}
                labelId={`label-select-${label}`}
                value={value}
                MenuProps={{
                  anchorOrigin: {
                    vertical: "bottom",
                    horizontal: "left"
                  },
                  getContentAnchorEl: null
                }}
                onChange={handleInputSelectChange}
              >
                {combindedOptions.map((option, index) => (
                  <MenuItem key={option.title} value={`${option.value}`}>
                    {option.title}
                  </MenuItem>
                ))}
              </Select>
            )}
          />
        </FormControl>
      )}
    </>
  );
}

...

为了给出一个更好的例子,我提供了一个 CSB:

您正在将 value 存储在它自己的 SelectOrInput 组件状态中。您需要将状态提升到父组件才能在父组件中获得价值。

  1. 在父组件中创建状态并使用默认值初始化并创建函数来更改它的值
  const [inputValue, setInputValue] = useState(null);

  const onChange = (value) => {
    setInputValue(value);
  };
  1. SelectOrInput 组件中传递 onChange 函数,并在值更改时调用 onChange 函数
<SelectOrInput
  ...
  onChange={onChange}
/>

// call onChange in handleInputSelectChange method

  const handleInputSelectChange = (
    event: React.ChangeEvent<{ value: unknown }>
  ): void => {
    const value = event.target.value as string;
    if (value === "") {
      const newState = !showCustomInput;
      setShowCustomInput(newState);

      setValue(value);
      onChange(value);  
    } else {
      setValue(value);
      onChange(value);
    }
  };

工作示例:https://codesandbox.io/s/dynamic-input-select-wk2je

在@Priyank Kachhela 的大力帮助下,我找到了答案。

通过提升状态到它最接近的共同祖先并删除child组件内的任何Controller组件。

App.js

  1. 在父组件中创建状态并使用默认值初始化并创建函数来更改它的值
 const [inputValue, setInputValue] = useState("null");

 const onSubmit = (data) => {
    // Stringify Object to always see real value, not the value evaluated upon first expanding.
    // 
    console.log(JSON.stringify(data, 4));
  };

  const onChange = (value) => {
    setInputValue(value);
  };
  1. Controller 包装 SelectOrInput 并将 onChange 函数、value 以及默认值传递给 Controller。然后使用render方法,在SelectOrInput组件上传播field

<Controller
  name={`contactPerson[0].salutation`}
  defaultValue={defaultValues.salutation}
  onChange={onChange}
  value={inputValue}
  control={control}
  render={({ field }) => (
    <SelectOrInput
     {...field}
     variant="outlined"
     selectOptions={customSalutationOptions}
     />
  )}
/>

components/SelectOrInput.js

  1. Bubble / (Call) onChange 每当从 Child-(SelectOrInput) 组件中更改值时的事件处理程序。
const handleInputSelectChange = (
    event: React.ChangeEvent<{ value: unknown }>
  ): void => {
    const value = event.target.value as string;
    if (value === "") {
      const newState = !showCustomInput;
      setShowCustomInput(newState);
      // Bubble / (Call) Event
      onChange(value);
    } else {
      onChange(value);
    }
  };

  const resetCustomInputToSelect = (event: React.MouseEvent<HTMLElement>) => {
    const newState = !showCustomInput;
    // Bubble / (Call) Event
    onChange("null");
    setShowCustomInput(newState);
  };
  1. 从 'SelectOrInput'
  2. 中删除组件内部输入状态

工作示例

在 Gist 中捕获的修订

https://gist.github.com/kkroeger93/1e4c0fe993f1745a34fb5717ee2ff545/revisions