2

I'm having an Ant Design <Form> component with <Form.Items> which have onChange events. If the onChange event function is true I'm displaying extra content. So in the example sandbox I created, when changing all the the <Radio> to Yes it fires the onChange event which is validated and then showing a div with the text "You checked all answered with yes". As I'm using <Form> it is a form controlled environment so I'm using form to set and reset values. But when calling form.resetFields() the onChange handlers are not called. So the message won't go away as the state not refreshes. So I have to find a way to call a function from the parent component which refreshes the form values in the child component. Using useImperativeHandle() for every field to update on more complex forms to call functions from the parent seems way too complex for such a simple task. And adding custom events to communicate with parent components seem to be a not very react way when reading this stack overflow thread Is there something from the Ant Design form I'm missing? Because this must be a common task. What's a good way to approach this problem?

Link to code sandbox with an example:

https://codesandbox.io/s/vigilant-curran-dqvlc?file=/src/AntDFormChild.js

Example

const formLayout = {
  labelCol: { span: 8 },
  wrapperCol: { span: 7 }
};

const questionDefaultValues = {
  rjr01_q01: 2,
  rjr02_q01: 2
};

const AntDForm = () => {
  const [form] = Form.useForm();

  const handleResetForm = () => {
    form.resetFields();
    // now force onChange of child component to update
  };

  const handleFillForm = () => {
    form.setFieldsValue({ rjr01_q01: 1, rjr02_q01: 1 });
    // now force onChange of child component to update
  };

  return (
    <>
      <Button onClick={handleResetForm}>Reset Form</Button>
      <Button onClick={handleFillForm}>Fill Form</Button>
      <Form
        {...formLayout}
        form={form}
        initialValues={{ ...questionDefaultValues }}
      >
        <AntDFormChild form={form} />
      </Form>
    </>
  );
};

const questionQualifiedValues = {
  rjr01_q01: 1,
  rjr02_q01: 1
};

const AntDFormChild = ({ form }) => {
  const [isQualified, setIsQualified] = useState(false);
  const [questionFormValues, setQuestionFormValues] = useState({});

  useEffect(() => {
    if (shallowEqual(questionFormValues, questionQualifiedValues)) {
      setIsQualified(true);
    } else {
      setIsQualified(false);
    }
  }, [questionFormValues]);

  function shallowEqual(object1, object2) {
    const keys1 = Object.keys(object1);
    const keys2 = Object.keys(object2);
    if (keys1.length !== keys2.length) {
      return false;
    }
    for (let key of keys1) {
      if (object1[key] !== object2[key]) {
        return false;
      }
    }
    return true;
  }

  return (
    <>
      {isQualified && (
        <div style={{ color: "red" }}>You checked all answered with yes</div>
      )}
      <Form.Item name="rjr01_q01" label="Question 1">
        <Radio.Group
          onChange={(i) => {
            setQuestionFormValues((questionFormValues) => ({
              ...questionFormValues,
              rjr01_q01: i.target.value
            }));
          }}
        >
          <Radio value={1}>Yes</Radio>
          <Radio value={0}>No</Radio>
          <Radio value={2}>Unknown</Radio>
        </Radio.Group>
      </Form.Item>
      <Form.Item name="rjr02_q01" label="Question 2">
        <Radio.Group
          onChange={(i) => {
            setQuestionFormValues((questionFormValues) => ({
              ...questionFormValues,
              rjr02_q01: i.target.value
            }));
          }}
        >
          <Radio value={1}>Yes</Radio>
          <Radio value={0}>No</Radio>
          <Radio value={2}>Unknown</Radio>
        </Radio.Group>
      </Form.Item>
    </>
  );
};
BenjaminK
  • 653
  • 1
  • 9
  • 26

1 Answers1

3

Since AntD Form is uncontrolled, there is no way to trigger onChange event by calling resetFields, setFieldsValues. I think your goal is to show the message depending on form values, and the best way to do is to use Form.Item, which can access form state.

https://codesandbox.io/s/antd-form-item-based-on-other-item-ens59?file=/src/AntDFormChild.js

Richard Zhan
  • 460
  • 3
  • 10
  • 2
    Your're a hero! Thanks. Two interesting facts why i didn't invesitcate further in shouldUpdate, one ``only calls shouldUpdate if `({getFieldValue})` is included in the `` children and secondly and this is the most confusing part for me, when logging shouldUpdates`{(prevValues, currentValues)` when resetting the form `prevValues`and `currentValues`are the same. Which is why I thought it won't work. – BenjaminK Dec 01 '21 at 17:56
  • Do you know why they are the same and it still works? – BenjaminK Dec 01 '21 at 17:57