【问题标题】:Why does my Form Data Value does not correspond to submitted Input Value?为什么我的表单数据值与提交的输入值不对应?
【发布时间】:2021-05-11 10:46:54
【问题描述】:

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

现在我通过将表单数据更新为所选选项/输入值的值而陷入困境。表单数据值始终保持初始/默认值。

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

组件/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:

【问题讨论】:

    标签: javascript reactjs react-hook-form


    【解决方案1】:

    您将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

    【讨论】:

    • 谢谢,这看起来很完美。您能否出于学习原因尝试向我解释,为什么将值存储在SelectOrInput 中的自身状态中是错误的。据我了解,提升状态用于跨多个组件共享现有数据,以反映相同的变化数据。但这在我的情况下如何对应?
    • 使用自己的组件状态是完全可以的,但是如果您想要来自子组件的父组件中的一些数据,那么您应该在父组件中定义状态并传递函数来更改子组件中的状态。在这里,您需要使用子组件中的值,这就是在子组件中更改值时将值存储在父组件中的原因。
    • 对这种方法有一个想法;这个解决方案不是多余的吗,因为我们现在在SelectOrInput 以及App.js 组件内部拥有并管理一个状态?如果我没记错的话
    • 是的,我没有从 SelectOrInput 组件中删除状态。如果需要,可以将其替换为父组件中的值。
    【解决方案2】:

    在@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.
        // https://stackoverflow.com/questions/23429203/weird-behavior-with-objects-console-log
        console.log(JSON.stringify(data, 4));
      };
    
      const onChange = (value) => {
        setInputValue(value);
      };
    
    1. Controller 包裹SelectOrInput 并将onChange 函数、value 以及defaultValue 传递给Controller。然后使用render方法,将field传播到SelectOrInput组件上。
    
    <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 /(调用)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”中移除组件内部输入状态

    工作示例

    在 Gist 中捕获的修订

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-07-31
      • 1970-01-01
      • 1970-01-01
      • 2015-01-16
      • 2016-12-04
      • 2013-02-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多