The front-end Form is mainly used to solve the three major problems of data acquisition, data verification and data assignment. The solutions provided in this article can be used perfectly in the React framework, but the solutions can be used in any framework language.

The form components in the middle and background are not only input and SELECT, but also range selectors, date selectors, etc. These components are often wrapped in layers on the native components to achieve a more elegant UI and more useful interaction, and after layers of layers, the shadow of the native form elements may not be seen any more. For example, the following DOM structure could be styled as an input component if wrapped, even though the input is completely invisible.

<span>
  <span contentEditable></span>
</span>Copy the code

So in order to make it easier for you to understand, I start from the traditional native form, so that you can have a progressive process.

Introduction: Native form forms

The initial code looks like this, which is simple and comfortable to look at.

<form action="/api/post" method="post">
  username: <input name="username" />
  passowrd: <input name="password" />
  <button type="submit">submit</button>
</form>Copy the code

But when you start doing data validation, the form immediately becomes much more complex. Here it is: the code has doubled.

<script>
  function checkname(target) {
    const value = target.value;
    if (value.length < 10) {
      document.getElementById('username_msg').innerHTML = 'Length must be >10'
    } else {
      document.getElementById('username_msg').innerHTML = ' '}}function checkpassword(target) {
    const value = target.value;
    if(! Value. The match (/ ^ [/ w] 6 16th} {$/)) {document. GetElementById ('password_msg').innerHTML = 'Password must be 6-16 alphanumeric'
    } else {
      document.getElementById('password_msg').innerHTML = ' '}}function getInitData() {
    ajax({
      url:'/api/data', 
      success:function(data) {
        document.getElementById('username') = data.username;
    });
  }
  getInitData();
</script>

<form action="/api/post" method="post">
 username: <input name="username" onchange="checkname(this)"/>
           <span id="username_msg"></span>
 passowrd: <input name="password" onchange="checkpassword(this)"/>
           <span id="password_msg"></span>
 <button type="submit">submit</button>
</form>Copy the code

If the DOM part is also implemented with JS, you can basically only modify JS without moving the DOM structure, but also make JS complexity increased a lot.

All DOM structures in React are generated by JAVASCRIPT. JSX can also easily implement DOM structures. However, I use native forms as an example. I just want to say that native forms written in React are not much more elegant than native JS forms.

React’s native form form

Here’s what the React framework looks like in its simplest form.

class Demo extends React.Component {
  render() {
    return <form action="/api/post" method="post">
      username: <input name="username" />
      passowrd: <input name="password" />
      <button type="submit">submit</button>
    </form>
  }
}Copy the code

For example, if you want to implement automatic checksum assignment for checksum input, consider the following code.

class Demo extends React.Component {
  state = {
    username: ' ',
    password: ' ',
    usernameMsg: ' ',
    passwordMsg: ' '}; Checkname = e => {// Get data const value = e.target.value; This.setstate ({username: value,}); // Verify dataif (value.length < 10) {
      this.setState({
        usernameMsg: 'Length must be >10'}); }else {
      this.setState({
        usernameMsg: ' '}); }}; Checkpassword = e => {// Get data const value = e.target.value; // This. SetState ({password: value,}); // Verify dataif(! Value. The match (/ ^ [/ w] 6 16th} {$/)) {enclosing setState ({passwordMsg:'Password must be 6-16 alphanumeric'}); }else {
      this.setState({
        passwordMsg: ' '}); }}; handleSubmit = () => { ajax({ url:'/api/post',
      data: {
        username: this.state.username,
        password: this.state.password,
      },
      success: () => { 
        // success
      },
    });
  };
  renderConst {username, password, usernameMsg, passwordMsg} = this.state;return (
      <form action="/api/post" method="post">
        username: <input value={username} onChange={this.checkname} />
        <span>{usernameMsg}</span>
        passowrd: <input value={password} onChange={this.checkpassword} />
        <span>{passwordMsg}</span>
        <button type="submit"onClick={this.handleSubmit}> submit </button> </form> ); }}Copy the code

The code is a bit long, but it can basically sum up a phenomenon that in order to achieve form data acquisition and verification, it is basically inseparable from the method of onChange, and there are several form controls, you need to write several onChange. IO/Frankqian /p… Debugging)

This has nothing to do with the framework, because in order to do both assignment and validation with any framework, you basically have to bind onChange to the input. So if there was a universal tool that automatically did all the onChange binding for you and fixed the validation rules, wouldn’t all the form problems be solved? Yes, the universal forms solution was designed with that in mind!

Solution for all React form components

All components written in React can use this scheme. Even non-React systems can use redirection to solve problems.

Based on the principle that all form controls need to bind onChange for data acquisition and validation, I designed a Field tool. This tool will automatically bind value + onChange for you to solve the above long code problem.

const field = new Field(this);

field.init('username');Copy the code

Field. Init automatically returns value + onChange as follows:

{
  value: ""ƒ ()}Copy the code

The following diagram shows the relationship between Field and React.




Use Field to get data

import {Field} from '@alifd/next'; class Demo extends React.Component { field = new Field(this); handleSubmit = () => { console.log(this.field.getValues()); // get data}render() {
    const {init} = this.field;
    return<form> username: <input {... init('username')} /> passowrd: <input {... init('password')} />
      <button onClick={this.handleSubmit} >submit</button>
    </form>
  }
}Copy the code

This solves the data fetching problem of a form and makes the code much cleaner. Codepen. IO/Frankqian /p… You can debug yourself

Form validation

Now that the data is available, validation of the form is a no-hassle because validation only depends on the data. We just need to abstract away the centralized, fixed form of interactivity and validation rules.

There are three types of interaction

  • Input real-time verification, generally onChange triggered
  • Check when out of focus, usually onBlur is triggered
  • Trigger validation by custom operation, call API trigger yourself

Common validation rule abstractions

Rule name

describe

type

Trigger condition/data type

required Can’t be empty Boolean Undefined/null / / [] ""
pattern Checking regular expression regular
minLength Minimum length of a string/minimum number of arrays Number String/Number/Array
maxLength Maximum length of a string/maximum number of arrays Number String/Number/Array
length Exact length of string/exact number of array Number String/Number/Array
min The minimum value Number String/Number
max The maximum Number String/Number
format

Summary of common patterns

url/email/tel/number

String String
validator Custom check Function

The following form is weakly typed. For example, in an input field where you want the user to enter an integer, there are two types of value that can be returned

  • “123456”, the integer verification of String type is: /\d+/
  • Typeof Value === ‘Number’ typeof Value == ‘Number’

Requiring the user to return the Number type is not very friendly, so the Field validation logic handles the type problem rather than leaving it up to the user to determine.

As an interlude, we continue to look at the following Field + form code, which handles all functions of data fetching and form validation

import { Field } from '@alifd/next'; class Demo extends React.Component { field = new Field(this); handleSubmit = (e) => { e.preventDefault(); this.field.validate(); // Custom check console.log(this.field.getValues()); // get data}render() {
    const {init, getError} = this.field;
    
    return<form> username: <input {... init('username', {rules: { required: true, minLength: 10}})} />
      <span style={{color: 'red'}}>{getError('username'}</span> {/** error message **/} passowrd: <input {... init('password', {rules: {pattern: /^[\w]{6,16}$/, message:'Password must be 6-16 alphanumeric'
        }})} />
      <span style={{color: 'red'}}>{getError('password')} < / span > {/ * * * * error information /} < button onClick = {this. HandleSubmit} > validate < / button > < / form >}}Copy the code

That would have been 70 lines of code before and 24 lines would have done it, and it would have made the code a lot cleaner. IO/Frankqian /p…




How to use your own form component

Many React components are now wrapped on top of native components, and many components may not have form elements wrapped (for example, Fusion Select does not have a Select element and does its own drop-down box). But you can use it as long as the components you write also follow the rules of the form.

Basic rule: Value + onChange Controlled rule

This rule actually comes from native HTML components, and any component we write can use Field as long as it follows the standard.




Self-written components are more aesthetically pleasing and interactive than native form components. Codepen. IO/Frankqian /p…

More user-friendly features

There are other, more fine-grained rules to make your components better suited for advanced functionality, such as:

  • One-click reset clears all data. Because each component receives different data types, value=undefined is received in willReceiveProps
componentWillReceiveProps(nextProps) {
    if ('value' innextProps ) { this.setState({ value: nextProps.value === undefined? []: nextProps. Value // Set the cleaned value of the component})}}Copy the code

  • Only one onChange is thrown per interaction
    • For example, if an upload triggers hundreds of onchanges, the entire page will be rendered hundreds of times, which greatly affects performance



    • For sliders, if the onChange is triggered in real time while dragging the Slider, it can be very slow when dragging the Slider. Therefore, it is more reasonable to trigger the instant the mouse is released, and other drag events can be handed over to onProgress



Fusion Next form components are basic is in accordance with the standards implementation, Fusion can view the document in detail. The design/component/f… Pull it down to the bottom

The Form component keeps the experience up to date

We know that Field can solve the problems of data verification, acquisition and assignment, but it cannot solve the problems of UI and interaction. We need to control the layout and error display by ourselves.

Make the layout easier

The layout of the scene includes horizontal inline layout and vertical column layout, which can be easily done through the FormItem API

  • Vertical layout
<Form>
  <FormItem label="Username:">
    <Input name="first"  placeholder="first"/>
    <Input name="second" placeholder="second"/>
  </FormItem>
  <FormItem label="Password:" required>
    <Input htmlType="password" name="pass" placeholder="Please enter your password!"/>
  </FormItem>
  <FormItem label="">
    <Form.Submit>Submit</Form.Submit>
  </FormItem>
</Form>Copy the code




  • Horizontal layout
<Form inline>... </Form>Copy the code




  • Label the built-in
<Form labelAligin="inset">... </Form>Copy the code




Auxiliary error display

Automatically displays error messages when errors occur, without getError judgment. How each state is represented is implemented by the component itself. Reduce coupling with forms




Each component’s loading, success, and failure are implemented by the component itself. The Form only passes state to each component during verification, so that the Form does not care what each component should look like.

<Input state="error"/> // Error status <Input state="loading"<Input state="success"/> // success <DatePicker state="error"/> // Error statusCopy the code

Further optimize the Form to make it easier to use

Above we still use Field + Form together, the code basically looks like this.

import { Form, Input, Field, Button } from '@alifd/next';

const FormItem = Form.Item;

class Demo extends React.Component {
  field = new Field(this);
  handleSubmit = () => {
    this.field.validate();
  }
  render() {
    const {init} = this.field;
    return  <Form field={this.field}>
        <FormItem label="Username:"> <Input {... init('username', {
              rules: {required}
            })} />
        </FormItem>
        <FormItem label="Password:"> <Input {... init('password', {rules: {pattern:/[\w]{6,16}/}})} htmlType="password" />
        </FormItem>
        <FormItem label="">
            <Button onClick={this.handleSubmit} >Submit</Button>
        </FormItem>
    </Form>
  }
}Copy the code

After writing too much, you might think that every component needs to use init, that every component needs to write rules, and that you need to write a bunch of JSON data in JSX.

Is there a way to simplify the code further by making it easier to fetch and validate data?

Further integrate Field capabilities and weaken usage

In view of the above problems, we further optimize the Form, integrating the ability of Field into the Form, and further weakening the use of Field, so that we do not need to care about init/ fetch data and other issues. The code is as follows:

import { Form, Input, Button } from '@alifd/next';

const FormItem = Form.Item;

class Demo extends React.Component {
  handleSubmit = (values, errors) => {
    if(errors) {// Check errorreturn; } console.log(values) // get data}render() {
    return  <Form>
        <FormItem label="Username:" required>
            <Input name"username" />
        </FormItem>
        <FormItem label="Password:"The pattern = {/ [/ w] {June 16th} /} > < Input name ="password" htmlType="password" />
        </FormItem>
        <FormItem label="">
            <Form.Submit validate onClick={this.handleSubmit} >Submit</Form.Submit>
        </FormItem>
    </Form>
  }
}Copy the code

Several optimizations can be seen in the code above:

  1. Don’t worry about the Field usage, change to the Form API. The usage is simple and direct
  2. Using name to initialize data is also closer to the use of native forms and easier to understand.
  3. Verification function API, the code is more concise, readable

Afterword.

Form optimization must go beyond that, as more complex functionality will be encountered in the real business.

In order to be more convenient and quick, many businesses will abstract the commonly used component layout, and directly display the form dynamically in the front end through the way of JSON Schema spit out by the back-end interface. Although it is convenient and fast when compared with the business, it can greatly solve the efficiency problem.

Or make a common form class scenario into a business component or module template, and download it directly when using it. For example: Fusion.design /module? Cate…

There are many programs, there is always a suit for their own.

A link to the

  • The field component demo fusion. The design/component/f…
  • The demo form component fusion. The design/component/f…
  • Fusion Next Component Repository github.com/alibaba-fus…