React Form Handling

in react

React doesn't provide a standard method to write forms, and that gap leaves many developers searching for ready-made solutions. Problem is many of the existing solutions feel over engineered or not flexible enough.

I think the reason is React apps (and developers) have different viewpoints, and so it's not easy to create a single solution that fits all.

Instead it may be better to build your own form components or even multiple sets of form components to fit each part of the application. In this post I'd like to present some guidelines for doing that.

Form Components Requirements

I find it more robust to write many small components and stay away from huge generic ones. Inevitable requirements changes are hard to predict, and when components are small it's easier to throw them away and replace with better ones.

A component based form would therefore look something like this:

<Form onSubmit={this.saveToServer} >
    <TextField name='name' label='User Name' />
    <EmailField name='email' label='Email' />
</Form>

This structure is very flexible: We put common fields (such as a CSRF token) in the Form component, and each sub field such as EmailField is small and self contained.

Next we need to talk about state management. The 3 main alternatives for handling the state of forms are:

  1. Save all input values in Form state. Pass it to children as props.
  2. Save all input values externally (flux store, redux, etc.)
  3. Have each input save its own data. Read all values on the submit handler (using refs).

The third option works best when form submission is not Ajax based and little or no validation is required. This allows reusing field structure but almost no functionality.

The second option works best when the fields are complex or are affected by external events. For example if incoming data from a web socket enables or disables certain fields.

The first option is my favorite as I find it provides the most flexible and simple solution. Common functionality is written in Form element, and the fields are self contained. Common field logic can be written as higher order component.

Saving State in Form

As each component is placed inside Form, it's the natural place to hold all their values. Simple validations can be performed in each component, and more complex validations (that combine several fields) are placed in the form itself.

Using validate.js here's a contact form which would use the described scheme. Note how we pass a validation schema to the form component:

class ContactForm extends React.Component {
  constructor(props) {
    super(props);
    this.printFormData = this.printFormData.bind(this);
  }
  
  printFormData(e, formData, valid) {
    e.preventDefault();
    console.log('--------- submit called. valid = ' + valid);
    console.dir(formData);
  }
  
  formSchema() {
    return {
      name: { presence: true },
      email: { presence: true,  email: true },
    }
  }
  
  render() {
    return (
      <Form onSubmit={this.printFormData} schema={this.formSchema()} >
        <TextField name='name' label='User Name' />
        <EmailField name='email' label='Email' />
      </Form>);
  }
}

A full working example with the Form source code is available below. Click Save to see validation results:

See the Pen react forms demo (breaking to smaller components) by Ynon Perek (@ynonp) on CodePen.

What's Next

React makes it really easy to build the wrong component hirearchies: Either because they're too complex or they don't cover each future requirement.

Luckily it also makes it very easy to delete components and replace them with new ones. That's why I prefer small composable components.

Handling forms should be performed with the same principles in mind. Use complex components as submission and validation logic grows complex, but prefer simple components where possible.

Comments