What’s a form? What can he do?

Forms are a familiar concept to most of us. They are common in the context of information input and allow users to submit data, modify data, or perform other, more complex interactions. Today we are going to talk about how the various Form scenarios used in development have evolved. WebForms, as defined on wikipedia, send user-input data to the server for processing. Baidu Encyclopedia, on the other hand, divides forms into form tags, form fields, and form buttons, which are used to transfer data to the CGI script on the server or cancel input. They can also be used to control other processing tasks that define processing scripts.

The author’s understanding of the function of forms is that forms provide users with the ability to interact with server data through UI view, and at the same time provide verification, error handling and the ability to display different types of data. In backend projects, forms have become an important form of interaction, where users fill in data, convert forms into JSON data structures, and interact with the server.

Move beyond native to React

Although our title is React forms, we’ll start with native HTML forms. First, the Form tag in HTML will get the corresponding user input according to the name of each Form element, and the Form will automatically process the Submit event (submit event is usually triggered by the input of Type =submit or the element of button). The form. novalidate attribute indicates that the Form does not need to be validated when it is submitted, or if it is not declared. The default method is GET, and the default action is the current URL. Event.target. elements returns all form elements.

Let’s look at the React implementation of forms. In fact, in a sense, other form schemes introduced later in this paper are also the encapsulation and expansion of React schemes.

In React, HTML form elements work a little differently than other DOM elements because form elements usually maintain some internal state.

React begins the form section like this, which leads to the first implementation: controlled components; Of course, a second implementation is also well known: uncontrolled components. The biggest difference between a controlled component and an uncontrolled component is the maintenance of internal state.

The controlled component does not maintain its own internal state. Externally it provides props and callback functions. The controlled component is controlled through props, and updates are notified to the parent component through onChange. Here’s an official example:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ' '};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('Submitted by name:' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>Name:<input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>); }}Copy the code

Uncontrolled components hand over the form data to the DOM node for processing, and manipulate the DOM through the Ref to get the form value and then manipulate it. The same example is implemented in an uncontrolled way:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.input = React.createRef();
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.current.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={this.input} />
        </label>
        <input type="submit" value="Submit" />
      </form>); }}Copy the code

The official attitude is to prefer controlled components (uncontrolled is in the higher guidelines). Developers for their contrast is also very much, here is a widespread contrast figure, but this picture is not entirely accurate, such as you might think that uncontrolled components through binding events can do instant form validation features, the author controlled and uncontrolled more is a kind of design specifications, mainly to see if the value of the subsidiary to the parent component module control, A controlled component is a controlled component and an uncontrolled component is not.

After scolding the senior, it became an official recommendation

However, the official solution is more rudimentary and does not provide the ability to validate, access fields, or handle form submissions, so the official solution Formik is also recommended at the end of the controlled Components section.

Formik, the official form solution, begins the document by explaining Formik’s main purpose: to help developers get form status, handle validation, error messages, and handle form submissions. It then points out the shortcomings of its predecessor, Redux-Form, which can be summarized as: Form state is transient and local in nature, so tracking it in Redux (or any type of Flux library) is not necessary; And a project that doesn’t use Redux because redux-form introduces Redux is unnecessary; And redux-Form calls your entire top-level Redux Reducer every time a Form item changes. As Redux applications grow, performance will deteriorate.

The Redux-Form process is shown below. Redux-form uses the formReducer to capture the application’s operations to inform how to update the Redux store. When the user action input, Focus action is distributed to Action Creators, the Redux Actions created by Action Creators are already bound with Dispatcher, The formReducer then updated the corresponding state slice, and finally the state was passed back to the text input box.

However, since all operations trigger Redux updates, this is too heavy (and pre-Redux-Form V6 was a full update with poor performance). Formik abandoned Redux because of these shortcomings and chose to maintain form state itself. Let’s start with an official example of Formik:

// Render Prop
 import React from 'react';
 import { Formik, Form, Field, ErrorMessage } from 'formik';
 
 const Basic = () = > (
   <div>
     <h1>Any place in your app!</h1>
     <Formik
       initialValues={{ email:"',password:"'}}validate={values= >{ const errors = {}; if (! values.email) { errors.email = 'Required'; } else if ( ! /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email) ) { errors.email = 'Invalid email address'; } return errors; }} onSubmit={(values, { setSubmitting }) => { setTimeout(() => { alert(JSON.stringify(values, null, 2)); setSubmitting(false); }, 400); }} > {({ isSubmitting }) => (<Form>
           <Field type="email" name="email" />
           <ErrorMessage name="email" component="div" />
           <Field type="password" name="password" />
           <ErrorMessage name="password" component="div" />
           <button type="submit" disabled={isSubmitting}>
             Submit
           </button>
         </Form>
       )}
     </Formik>
   </div>
 );
 
 export default Basic;
Copy the code

Formik’s main design idea is to help developers reduce template code by providing a few additional components:

Formik also handles the submission of the form by calling handleSubmit(e) on line 794 or submitForm on line 729 (both methods are provided as attributes in Formik). When calling one of these methods, Formik first touches all the fields and then sets Iscoby to true. Validation is then performed to set isValidating to True and asynchronously run all field-level validation and validationSchema, and deeply merge the execution results to determine if there are any errors. If there are errors, cancel the submission, set isValidating to False, set the error message, and set IScoby to false. If there are no errors, set isValidating to False and commit.

Formik also uses FastField for performance optimization, but if you look at line 75 of the FastField. TSX source code, you’ll see that Formik simply checks the key states in the form values, errors, The touched combines the lengths of props and key fields such as ISbiographies with shouldComponentUpdate, which is ineffective for complex scenarios such as linkage transformations.

All in all, Formik is free from Redux, allows developers to write less template code, and has some performance improvements, so it’s not surprising that Formik is officially recommended. But there is still a lot of room for improvement in terms of code architecture that does not support finer granularity of updates.

Redux-form’s react-final-form is a react-form, and redux-Form’s react-final-form documentation gives developers priority over react-final-form. The only scenario where redux-form is still worth using is if you need to tightly couple Form data to Redux, especially if you need to subscribe to it but don’t plan to modify it from parts of the application. But before we look at react-final-form let’s see what Antd from ants does.

Antd 3.x — Advance by groping

Let’s start with Antd 3.x, but let’s take a look at the official example:

import { Form, Icon, Input, Button } from 'antd';

function hasErrors(fieldsError) {
  return Object.keys(fieldsError).some(field= > fieldsError[field]);
}

class HorizontalLoginForm extends React.Component {
  componentDidMount() {
    // To disable submit button at the beginning.
    this.props.form.validateFields();
  }

  handleSubmit = e= > {
    e.preventDefault();
    this.props.form.validateFields((err, values) = > {
      if(! err) {console.log('Received values of form: ', values); }}); };render() {
    const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.props.form;

    // Only show error after a field is touched.
    const usernameError = isFieldTouched('username') && getFieldError('username');
    const passwordError = isFieldTouched('password') && getFieldError('password');
    return (
      <Form layout="inline" onSubmit={this.handleSubmit}>
        <Form.Item validateStatus={usernameError ? 'error' :"'}help={usernameError| | '} >
          {getFieldDecorator('username', {
            rules: [{ required: true, message: 'Please input your username!' }],
          })(
            <Input
              prefix={<Icon type="user" style={{ color: 'rgba(0.0.0.25.) '}} / >}
              placeholder="Username"
            />,
          )}
        </Form.Item>
        <Form.Item validateStatus={passwordError ? 'error' :"'}help={passwordError| | '} >
          {getFieldDecorator('password', {
            rules: [{ required: true, message: 'Please input your Password!' }],
          })(
            <Input
              prefix={<Icon type="lock" style={{ color: 'rgba(0.0.0.25.) '}} / >}
              type="password"
              placeholder="Password"
            />,
          )}
        </Form.Item>
        <Form.Item>
          <Button type="primary" htmlType="submit" disabled={hasErrors(getFieldsError())}>
            Log in
          </Button>
        </Form.Item>
      </Form>); }}const WrappedHorizontalLoginForm = Form.create({ name: 'horizontal_login' })(HorizontalLoginForm);

ReactDOM.render(<WrappedHorizontalLoginForm />, mountNode);
Copy the code

In fact, after reading this code, some questions arise, such as why form.create () is used to wrap the Form and why getFieldDecorator is used to wrap the data. To solve these problems, we must pay attention to Antd 3.x Form’s underlying dependency on RC-Form.

First, let’s look at what form.create () does. The component Form code has a static create method that returns the createDomForm from rC-Form when called. This method takes as arguments some of the methods in mixins (which, as shown below, come with getFieldDecorator and other attributes and methods), passes in the createBaseForm method and generates a new container through the higher-level component Class.

The static properties of the wrapped component are then copied into the new component. Perform life cycle events, primarily by initializing the default field via getInitialState. The Render function returns the original component that has been injected with the Form component’s properties. The returned new component is wrapped in argumentContainer, which uses the hoist non-react-statics library’s hoistStatics method, This method is used to combine static methods from an incoming component into a new component. This completes the Form’s initialization, and the RC-Form creates a fieldStore for the component instance that holds the current Form’s various states.

Then there is getFieldDecorator, which is responsible for data management. After the user passes the key, the initial value, and the validation rule using the getFieldDecorator method, It creates the form message via getFieldProps to return a clone of the INPUT back to fieldsStore (line 225 of the source code) and binds the default onChange event.

If validation is required, onCollectValidate is triggered, the result is saved to fieldsStore via validateFieldsInternal, and the form component is returned with two-way data binding.

When a bidirectional binding form item triggers onChange or onBlur, the corresponding onCollect method is called (for example, onCollectValidate is used for validation), and the corresponding onChange is triggered by internal call onCollectCommon. Gets the updated value in the event, generates a new field, and setFields calls forceUpdate to set the updated value. The data flows back and the end user sees the new value.

Rc-form seems to take over a lot of detail, reducing developer burden. However, when setFields calls forceUpdate to set the update value, it creates a global rendering of the entire Form, which can cause performance problems. This is addressed in Antd 4.x and will be discussed in the next section.

Reference documentation

  1. Reactjs.org/docs/forms….
  2. Github.com/redux-form/…
  3. Formik.org/docs/overvi…
  4. Github.com/formium/for…
  5. 3 x. Ant. The design/components /…
  6. Github.com/mridgway/ho…

Author: ES2049 / Dibao

The article can be reproduced at will, but please keep the original link.

You are welcome to join ES2049 Studio. Please send your resume to [email protected].