表单中的受控输入在提交时抛出错误

Controlled input in form throwing error upon submission

我一直在尝试使用具有受控输入的表单提交登录请求。 submit 函数向下传递到 React 组件,在 material-ui ButtononClick 时触发。仅当我使用 Apollo Client 发送变更请求时才会抛出此错误。

index.js:1375 Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.

根据我对 controlled components in React's docs 的理解,input 组件是 "controlled" 通过在 value 中使用 React valuesetValue state hooks 和onChange 个属性。

这是顶级 Login 组件,包含 submit 函数和 useMutation 挂钩。 submit 首先传递给 LoginForm 组件。

const Login = () => {
  const [login, { data }] = useMutation(LOGIN);
  console.log(data);

  const submit = async form => {
    console.log(form); // form object looks correct
    await login({ variables: form });
  };

...

    <Container>
      <LoginForm submit={submit} />
    </Container>

这是 LoginForm 组件,它呈现 GeneralForm 组件。同样,submit 传递给 GeneralForm

const fields = [
  {
    id: "username",
    label: "Username",
    required: true,
    placeholder: "example: 98sean98"
  },

...

const LoginForm = props => {
  const { submit } = props;

...

    <Container>
      <GeneralForm fields={fields} submit={submit} />
    </Container>

这是 GeneralForm 组件。

const GeneralForm = props => {
  const { fields, submit } = props;
  const [form, setForm] = useState({});

  useEffect(() => {
    fields.forEach(field => {
      form[field.id] = "";
    });
    setForm(form);
  }, [form, fields]);

  const handleChange = event => {
    form[event.target.id] = event.target.value;
    setForm(setForm);
  };

  const handleSubmit = () => {
    if (validateForm(form)) { // returns a boolean indicating validity
      submit(form); // trigger the submit function that is passed down from <Login />
    } else {
      alert("invalid form");
    }
  };

  return (
    <FormGroup>
      {fields.map(field => (
        <FormControl key={field.id} required={field.required}>
          <InputLabel htmlFor={field.id}>{field.label}</InputLabel>
          <Input
            required={field.required}
            id={field.id}
            type={field.type ? field.type : "text"}
            aria-describedby={
              field.helperText ? `${field.id}-helper-text` : null
            }
            placeholder={field.placeholder}
            value={form[field.id]}
            onChange={handleChange}
          />
          {field.helperText ? (
            <FormHelperText id={`${field.id}-helper-text`}>
              {field.helperText}
            </FormHelperText>
          ) : null}
        </FormControl>
      ))}

      <Button type="submit" onClick={handleSubmit}>
        Submit
      </Button>
    </FormGroup>
  );
};

我的开发环境

partial packages list:
  "@apollo/react-hooks": "^3.1.3",
  "@material-ui/core": "^4.7.0",
  "@material-ui/icons": "^4.5.1",
  "apollo-boost": "^0.4.4",
  "graphql": "^14.5.8",
  "react": "^16.10.2",
  "react-dom": "^16.10.2",

Machine: MacOS X Catalina 10.15.1

我现在观察到的特殊行为是在不调用 Apollo Client 变更请求的情况下,

const submit = async form => {
  console.log(form);
  // await login({ variables: form });
};

不会触发上述错误。所以,我想知道 Apollo Client 是否以某种方式错误地更改了我的 form 对象。

我花了一些时间在 Internet 上搜索,这个 resource 似乎很有帮助。显然,我所要做的就是将 Inputvalue 属性切换为监听由 React 状态在同一组件中公开的 value,而不是向下传递的 form[field.id]来自另一个组件。

// GeneralForm.js
...
const [value, setValue] = useState(form[field.id] ? form[field.id] : "");
...
<Input value={value} ... />

因此,我将 Input 组件及其父组件 FormControl 模块化到另一个名为 FormInput.js 的文件中,并得出了这个解决方案。

// FormInput.js
const FormInput = props => {
  const { field, form, setForm } = props;
  const [value, setValue] = useState(form[field.id] ? form[field.id] : "");

  const handleChange = event => {
    setValue(event.target.value);
    setForm({
      ...form,
      [event.target.id]: event.target.value
    });
  };

  return (
    <FormControl key={field.id} required={field.required}>
      <InputLabel htmlFor={field.id}>{field.label}</InputLabel>
      <Input
        required={field.required}
        id={field.id}
        type={field.type ? field.type : "text"}
        aria-describedby={field.helperText ? `${field.id}-helper-text` : null}
        placeholder={field.placeholder}
        value={value}
        onChange={handleChange}
      />
      {field.helperText ? (
        <FormHelperText id={`${field.id}-helper-text`}>
          {field.helperText}
        </FormHelperText>
      ) : null}
    </FormControl>
  );
};

然后,我将 FormInput 导入 GeneralForm,传递所有必要的道具。