Antd Select Form Item keyboard behavior problem in dynamic nested components

730 views Asked by At

I have a nested dynamic form.

Currently if I input something in the city field, then tab to the state field and type two letters for a state, then tab to the next field, the state field does not stay populated. I would like it to stay populated with my state. Pretty simple common behavior. Example screenshots:

Before

As soon as I tab to the next field, State returns to blank:

After

I believe that essentially the way to do this is to use the Select element's onblur option to populate it when the user tabs out. In fact I used to have it working when it was not a nested component here. Unfortunately when I refactored it to make it nested, i broke it. I think my ref broke and I can't figure out how to use forwardref to fix it but i could be way off here. refs kind of make my head spin.

Hoping some kind soul out there will help me understand how to fix it. I'm also now getting a warning that each child in a list should have a unique "key" prop and am unsure why.

Parent Component TenantForm.js

import React from "react";
import moment from "moment";
import { Form, Input, Select } from "antd";
import ArrayOfSubFormComponentsForApplicants from "./ArrayOfSubFormComponentsForApplicants";

const rules = [{ required: true }];

const validateMessages = {
  required: "Required!",
  types: {
    email: "Invalid E-mail!",
    number: "Invalid Number!",
  },
  number: {
    range: "Must be between ${min} and ${max}",
  },
};

const formItemLayoutWithOutLabel = {
  wrapperCol: {
    xs: { span: 36, offset: 0 },
    sm: { span: 16, offset: 6 },
  },
};

export default class TenantForm extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      tenant: {
        applicants: [
          {
            firstName: props.tenant ? props.tenant.first : "",
            middleName: props.tenant ? props.tenant.middle : "",
            lastName: props.tenant ? props.tenant.last : "",
            street: props.tenant ? props.tenant.street : "",
            city: props.tenant ? props.tenant.city : "",
            state: props.tenant ? props.tenant.state : "",
            zipcode: props.tenant ? props.tenant.zipcode : "",
          },
        ],
        error: "",
      }
    };
  }  
    
  handleFieldChange = (field) => (event, value, selectedKey) => {
    // make a copy of the object first to avoid changes by reference
    let data = { ...this.state.data };  
  
    // use here event or value of selectedKey depending on your component's event
    data[field] = value;  
  
    this.setState({ data });
  }

  render() {
    const onFinish = (values) => {
      console.log("onFinish: ", values);
      this.props.onSubmit({
        applicants: values.applicants,
      });
    };
    return (
      <Form
        {...formItemLayoutWithOutLabel}
        onFinish={onFinish}
        id="myForm"
        validateMessages={validateMessages}
        initialValues={{ applicants: [{ firstName: "" }] }}
      >
        <ArrayOfSubFormComponentsForApplicants
          value={this.state.tenant.applicants}
          onChange={this.handleFieldChange('applicants')}
        />
      </Form>
    );
  }
}

Child Component ArrayOfSubFormComponentsForApplicants.js

import React from "react";
import SubFormComponentForApplicants from './SubFormComponentForApplicants';

export default class ArrayOfSubFormComponentsForApplicants extends React.Component {
  
  handleFieldChange = (index) => (event, value, selectedKey) => {
    let data = [...this.props.value];
    data[index] = value;
    this.props.onChange(null, data);
  }

  render() {
    return (
      <div>
        {this.props.value.map((subform, index) =>
          <SubFormComponentForApplicants
            key={subform.key}
            value={subform}
            onChange={this.handleFieldChange(index)}
          />
        )}
      </div>
    )
  }
}

export default ArrayOfSubFormComponentsForApplicants

Child Component SubFormComponentForApplicants.js

import React from "react";
import moment from "moment";
import { Form, Input, Col, Row, Select, Button, DatePicker, Switch } from "antd";
import { PlusOutlined, DeleteTwoTone } from "@ant-design/icons";

const InputGroup = Input.Group;
const Option = Select.Option;
const { TextArea } = Input;
const maxApplicants = 4;

const rules = [{ required: true }];

const STATES = [
  "AL",
  "AK",
  "AZ",
  "AR",
  "CA",
  "CO",
  "CT",
  "DE",
  "DC",
  "FM",
  "FL",
  "GA",
  "GU",
  "HI",
  "ID",
  "IL",
  "IN",
  "IA",
  "KS",
  "KY",
  "LA",
  "ME",
  "MH",
  "MD",
  "MA",
  "MI",
  "MN",
  "MS",
  "MO",
  "MT",
  "NE",
  "NV",
  "NH",
  "NJ",
  "NM",
  "NY",
  "NC",
  "ND",
  "MP",
  "OH",
  "OK",
  "OR",
  "PW",
  "PA",
  "PR",
  "RI",
  "SC",
  "SD",
  "TN",
  "TX",
  "UT",
  "VT",
  "VI",
  "VA",
  "WA",
  "WV",
  "WI",
  "WY",
];

const validateMessages = {
  required: "Required!",
  types: {
    email: "Invalid E-mail!",
    number: "Invalid Number!",
  },
  number: {
    range: "Must be between ${min} and ${max}",
  },
};

const formItemLayout = {
  labelCol: {
    xs: { span: 24 },
    sm: { span: 6 },
  },
  wrapperCol: {
    xs: { span: 36 },
    sm: { span: 16 },
  },
};

const formItemLayoutWithOutLabel = {
  wrapperCol: {
    xs: { span: 36, offset: 0 },
    sm: { span: 16, offset: 6 },
  },
};

class SubFormComponentForApplicants extends React.Component {
  
  constructor(props) {
    super(props);

    this.state = {
      applicants: [
        {
          firstName: props.tenant ? props.tenant.first : "",
          middleName: props.tenant ? props.tenant.middle : "",
          lastName: props.tenant ? props.tenant.last : "",
          street: props.tenant ? props.tenant.street : "",
          city: props.tenant ? props.tenant.city : "",
          state: props.tenant ? props.tenant.state : "",
          zipcode: props.tenant ? props.tenant.zipcode : "",
        },
      ],
      error: "",
    };
  }

  render() {
    return (
          <Form.List name="applicants">
            {(fields, { add, remove }) => {
              return (
                <div>
                  {fields.map((field, index) => (
                    <Form.Item {...formItemLayout} label={`Applicant #${index + 1}`} key={field.key}>
                      <Row key={field.key} gutter={[0, 8]} justify="start">
                        <Col>
                          <Row gutter={[4, 4]}>
                            <Col span={7}>
                              <Form.Item name={[field.name, "firstName"]} fieldKey={[field.fieldKey, "firstName"]} rules={rules}>
                                <Input placeholder="First Name" />
                              </Form.Item>
                            </Col>
                            <Col span={7}>
                              <Form.Item name={[field.name, "middleName"]} fieldKey={[field.fieldKey, "middleName"]} initialValue="">
                                <Input placeholder="Middle Name" />
                              </Form.Item>
                            </Col>
                            <Col span={9}>
                              <Form.Item name={[field.name, "lastName"]} fieldKey={[field.fieldKey, "lastName"]} rules={rules}>
                                <Input placeholder="Last Name" />
                              </Form.Item>
                            </Col>
                            <Col flex="none">
                              {index > 0 ? (
                                <DeleteTwoTone
                                  className="dynamic-delete-button"
                                  onClick={() => {
                                    remove(field.name);
                                  }}
                                />
                              ) : null}
                            </Col>
                          </Row>
                          <Row gutter={[4, 4]}>
                            <Col span={23}>
                              <Form.Item name={[field.name, "address"]} fieldKey={[field.fieldKey, "address"]} rules={rules}>
                                <Input placeholder="Address" />
                              </Form.Item>
                            </Col>
                          </Row>
                          <Row gutter={[4, 4]}>
                            <Col span={12}>
                              <Form.Item name={[field.name, "city"]} fieldKey={[field.fieldKey, "city"]} rules={rules}>
                                <Input placeholder="City" />
                              </Form.Item>
                            </Col>
                            <Col span={5}>
                              <Form.Item name={[field.name, "state"]} fieldKey={[field.fieldKey, "state"]} rules={rules}>
                                <Select
                                  showSearch
                                  placeholder="State"
                                  defaultActiveFirstOption={true}
                                >
                                  {STATES.map((state) => (
                                    <Option value={state} key={state}>
                                      {state}
                                    </Option>
                                  ))}
                                </Select>
                              </Form.Item>
                            </Col>
                            <Col span={6}>
                              <Form.Item name={[field.name, "zipcode"]} fieldKey={[field.fieldKey, "zipcode"]} rules={rules}>
                                <Input placeholder="Zip Code" />
                              </Form.Item>
                            </Col>
                          </Row>
                        </Col>
                      </Row>
                    </Form.Item>
                  ))}
                  <Form.Item>
                    {fields.length < maxApplicants ? (
                      <Button
                        type="dashed"
                        onClick={() => {
                          add();
                        }}
                        style={{ width: "100%" }}
                      >
                        <PlusOutlined /> Add Applicant
                      </Button>
                    ) : null}
                  </Form.Item>
                </div>
              );
            }}
          </Form.List>
    );
  }
}

export default SubFormComponentForApplicants;
0

There are 0 answers