Search code examples
javascriptreactjsantd

Antd - How to change Select's initialValue based on sibling's getFieldValue in a Form?


In my form, there are 2 children per row in a list: Input and Select

In my input, if I type a set of a string that, when passed into regex, returns either a phone number, url or email, I want the Select component to automatically pre-select this field.

I am watching the Input's getFieldValue for what type I should select from the Select component.

The problem I am seeing is that initialValue for Select is only set on render (kinda makes sense based on it's name). I can't however change it afterwards and I don't know how I can programmatically call Select's onChange handler from the Input's getFieldValue.

Is this possible?

codesandbox

import React from "react";
import "./index.css";
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
import { Button, Form, Input, Select, Space } from "antd";

const { Option } = Select;

const options = [
  { label: "Work", value: "work" },
  { label: "Home", value: "home" },
  { label: "Mobile", value: "mobile" },
  { label: "URL", value: "url" },
  { label: "Email", value: "email" }
];

const validateCurrentInputValueType = (value: string) => {
  const isOnlyDigitsRegex = /^\d+$/;
  const isURLRegex = /^(?:https?:\/\/)?(?:www\.)?[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+[^\s]*$/;
  const isEmailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

  if (isOnlyDigitsRegex.test(value)) return "mobile";
  if (isURLRegex.test(value)) return "url";
  if (isEmailRegex.test(value)) return "email";

  // return "mobile";
};

const App: React.FC = () => {
  const [form] = Form.useForm();

  const onFinish = (values: any) => {
    console.log("Received values of form:", values);
  };

  const handleChange = () => {
    const vals = form.getFieldsValue();
    console.log("vals", vals);
  };

  return (
    <Form form={form} name="info-form" onFinish={onFinish} autoComplete="off">
      <Form.List name="info">
        {(fields, { add, remove }) => (
          <>
            {fields.map(({ key, name, fieldKey, ...restField }) => (
              <Space key={key} align="baseline">
                <Form.Item
                  label="Details"
                  {...restField}
                  name={[name, "info_input"]}
                  fieldKey={[fieldKey, "info_input"]}
                >
                  <Input />
                </Form.Item>
                <Form.Item shouldUpdate style={{ marginLeft: 8, width: 100 }}>
                  {({ getFieldValue }) => {
                    const currentInputValue = getFieldValue([
                      "info",
                      name,
                      "info_input"
                    ]);

                    console.log("currentInputValue", currentInputValue);
                    const type = validateCurrentInputValueType(
                      currentInputValue
                    );
                    console.log("type", type);

                    return (
                      <Form.Item
                        name={[name, "info_select"]}
                        fieldKey={[fieldKey, "info_select"]}
                      >
                        <Select onChange={handleChange}>
                          {options.map((option) => (
                            <Option key={option.value} value={option.value}>
                              {option.label}
                            </Option>
                          ))}
                        </Select>
                      </Form.Item>
                    );
                  }}
                </Form.Item>

                <MinusCircleOutlined onClick={() => remove(name)} />
              </Space>
            ))}

            <Form.Item>
              <Button
                type="dashed"
                onClick={() => add()}
                block
                icon={<PlusOutlined />}
              >
                Add field
              </Button>
            </Form.Item>
          </>
        )}
      </Form.List>
      <Form.Item>
        <Button type="primary" htmlType="submit">
          Submit
        </Button>
      </Form.Item>
    </Form>
  );
};

export default App;



Solution

  • You can validate the value in input onChange function and then set the value of select component. If validateCurrentInputValueType function does not return any value, it'll not set any value for select field.

    Here's the complete code.

    import React from "react";
    import "./index.css";
    import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
    import { Button, Form, Input, Select, Space } from "antd";
    
    const options = [
      { label: "Work", value: "work" },
      { label: "Home", value: "home" },
      { label: "Mobile", value: "mobile" },
      { label: "URL", value: "url" },
      { label: "Email", value: "email" }
    ];
    
    const validateCurrentInputValueType = (value: string) => {
      const isOnlyDigitsRegex = /^\d+$/;
      const isURLRegex = /^(?:https?:\/\/)?(?:www\.)?[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+[^\s]*$/;
      const isEmailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
    
      if (isOnlyDigitsRegex.test(value)) return "mobile";
      if (isURLRegex.test(value)) return "url";
      if (isEmailRegex.test(value)) return "email";
    
      // return "mobile";
    };
    
    const App: React.FC = () => {
      const [form] = Form.useForm();
    
      const onFinish = (values: any) => {
        console.log("Received values of form:", values);
      };
    
      return (
        <Form
          form={form}
          name="infoForm"
          onFinish={onFinish}
          autoComplete="off"
          onValuesChange={(changedValues, values) => {
            console.log(changedValues, values);
          }}
        >
          <Form.List name="info">
            {(fields, { add, remove }) => (
              <>
                {fields.map(({ key, name, ...restField }) => (
                  <Space key={key} align="baseline">
                    <Form.Item
                      label="Details"
                      {...restField}
                      name={[name, "info_input"]}
                    >
                      <Input
                        onChange={(e) => {
                          const type = validateCurrentInputValueType(
                            e.target.value
                          );
                          if (type) {
                            form.setFieldValue(["info", name, "info_select"], type);
                          }
                        }}
                      />
                    </Form.Item>
                    <Form.Item name={[name, "info_select"]}>
                      <Select options={options} placeholder="Select Value" />
                    </Form.Item>
                    <MinusCircleOutlined onClick={() => remove(name)} />
                  </Space>
                ))}
    
                <Form.Item>
                  <Button
                    type="dashed"
                    onClick={() => add()}
                    block
                    icon={<PlusOutlined />}
                  >
                    Add field
                  </Button>
                </Form.Item>
              </>
            )}
          </Form.List>
          <Form.Item>
            <Button type="primary" htmlType="submit">
              Submit
            </Button>
          </Form.Item>
        </Form>
      );
    };
    
    export default App;