Adding a new field to a Form

This article walks through the changes required to add a new field to an entity edit form. 

See Adding a new field to schema, form and display for the first part of this article.

We are using AntD Form components and you should review https://ant.design/components/form/ first.

Each form comprises three key sections

  1. Initialise - copy the entity data into the form fields
  2. Edit - define the input fields and validation rules
  3. Save - copy the results back into the entity and save it.


Entity Edit Forms in Voluntarily are all named EntityDetailForm.js. e.g. OrgDetailForm.js and will be found in the /components/{entity}/ folder.

Currently our forms are standard React Class components.  If you are interested in converting these to use hooks - let me know.


Initialisation

This takes place in Form.Create which you'll usually find at the bottom of the file.

export default Form.create({
  name: 'organisation_detail_form',
  onFieldsChange (props, changedFields) {
    // props.onChange(changedFields);
  },
  mapPropsToFields (props) {
    const org = props.org
    if (!org.info) { org.info = {} }
    return {
      name: Form.createFormField({ ...org.name, value: org.name }),
      about: Form.createFormField({ ...org.info.about, value: org.info.about }),
      followers: Form.createFormField({ ...org.info.followers, value: org.info.followers }),
      joiners: Form.createFormField({ ...org.info.joiners, value: org.info.joiners }),
      members: Form.createFormField({ ...org.info.members, value: org.info.members }),
      outsiders: Form.createFormField({ ...org.info.outsiders, value: org.info.outsiders }),
      imgUrl: Form.createFormField({ ...org.imgUrl, value: org.imgUrl }),
      website: Form.createFormField({ ...org.website, value: org.website }),
      contactEmail: Form.createFormField({ ...org.contactEmail, value: org.contactEmail }),
      facebook: Form.createFormField({ ...org.facebook, value: org.facebook }),
      twitter: Form.createFormField({ ...org.twitter, value: org.twitter }),
      category: Form.createFormField({ ...org.category, value: org.category })
    }
  },
  onValuesChange (_, values) {
    // console.log('onValuesChange', values)
  }
})(OrgDetailForm)


Form Create is a Higher Order Component (HOC) that wraps the Form Class and adds hooks for onFieldsChange, mapPropsToFields and onValuesChange.  Usually are only interested in mapPropsToFields.  This function creates a binding between the class 'props' value and the form fields.

We usually link the  input field value directly to the entity property but you can translate values at this point.

Edit

Each input field is defined in the render function using <Form.Item>  which at a minimum ties the input field to the fieldDecorator - which is handling the init from props and validation.

<Form.Item label='Twitter'>
	{getFieldDecorator('twitter')(<Input addonBefore='@' />)}
</Form.Item>


A more complex field may have validation rules

<Form.Item label={orgCategory}>
            {getFieldDecorator('category', {
              rules: [
                { required: true, message: 'category is required' }
              ]
            })(
              <Checkbox.Group
                options={categoryOptions}
              />
            )}


For example to enter a website name we may want to do a format check that the field looks like a URL.

{getFieldDecorator('website', {
              rules: [
                { pattern: /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'()*+,;=.]+$/,
                  message: 'Enter valid URL' }
              ]
            })(
              <Input placeholder='Organisation Website' />
            )}


Validate and Save

Finally when the form is submitted we need to validate the form and then save the results.  In our code we usually save by preparing the updated object and then calling a callback handler that has been passed in the props so that the parent page holding the form can decide how the save is done e.g. by making an API call. This keeps the form separated from the data movement.


Here we copy out values from the form fields and update the result object. We copy one field at a time so that we can check or transform each item and keep any values in the original object that should be preserved - such as _id.

handleSubmit = (e) => {
    e.preventDefault()
    this.props.form.validateFields((err, values) => {
      if (!err) {
        // preserve the id and other values not edited by form.
        const org = this.props.org
        // update the rest from the form values.
        org.name = values.name
        org.slug = slug(values.name)
        org.info.about = values.about
        org.info.followers = values.followers
        org.info.joiners = values.joiners
        org.info.members = values.members
        org.info.outsiders = values.outsiders
        org.imgUrl = values.imgUrl
        org.website = values.website
        org.twitter = values.twitter
        org.facebook = values.facebook
        org.contactEmail = values.contactEmail
        org.category = values.category

        window.scrollTo(0, 0)
        this.props.onSubmit(this.props.org)
      }
    })
  }

This code could also use the ... spread operator to merge the old and new values.


At the end of the handler we call the props.onSubmit to perform the save along with any feedback and changing the page to view the details.