Validation of complex forms React. Part 1

First you need to install the component react-validation-boo , I assume that you are familiar with react and know how to configure.

npm install react-validation-boo

In order not to talk a lot, I’ll immediately give you a small sample code.

import React, {Component} from'react';
import {connect, Form, Input, logger} from'react-validation-boo';
classMyFormextendsComponent{
    sendForm = (event) => {
        event.preventDefault();
        if(this.props.vBoo.isValid()) {
            console.log('Получаем введённые значения и отправляем их на сервер', this.props.vBoo.getValues());
        } else {
            console.log('Выведем в консоль ошибки', this.props.vBoo.getErrors());
        }
    };
    getError = (name) => {
        returnthis.props.vBoo.hasError(name) ? <divclassName="error">{this.props.vBoo.getError(name)}</div> : '';
    };
    render() {
        return<Formconnect={this.props.vBoo.connect}><div><Inputtype="text"name="name" />
                {this.getError('name')}
            </div><buttononClick={this.sendForm}>
                {this.props.vBoo.isValid() ? 'Можно отправлять': 'Будьте внимательны!!!'}
            </button></Form>
    }
}
export default connect({
    rules: () => (
        [
            ['name', 'required'],
        ]
    ),
    middleware: logger
})(MyForm);


Let's break this code down.

Let's start with the connect function , we pass our validation rules and other additional parameters to it. By calling this method, we get a new function in which we pass our component ( MyForm ) so that it receives the necessary methods for working with form validation in props .

In the render function of our component, we return the Form component which we connect to the validation rules connect = {this.props.connect} . This design is required for Form to know how to validate nested components.
<Input type = "text" name = "name" />The input field that we will check, we passed the verification rules to connect in the rules property . In our case, this name should not be empty ( required ).

We also passed the middleware: logger to connect in order to see how the validation takes place in the console. In the props of our component, we got a set of functions:



  1. vBoo.isValid () - returns true if all input components have been validated.
  2. vBoo.hasError (name) - returns true if the component with the name property is not valid
  3. vBoo.getError (name) - for a component with the name property , returns the error text

Now we will gradually complicate, to begin with, we will transfer the language to connect in order to change the validation rules depending on the language, as well as add additional fields and validation rules.

import React, {Component} from'react';
import {connect, Form, Input, InputCheckbox} from'react-validation-boo';
classMyFormextendsComponent{
    sendForm = (event) => {
        event.preventDefault();
        if(this.props.vBoo.isValid()) {
            console.log('Получаем введённые значения и отправляем их на сервер', this.props.vBoo.getValues());
        } else {
            console.log('Выведем в консоль ошибки', this.props.vBoo.getErrors());
        }
    };
    getError = (name) => {
        returnthis.props.vBoo.hasError(name) ? <divclassName="error">{this.props.vBoo.getError(name)}</div> : '';
    };
    render() {
        return <Form connect={this.props.vBoo.connect}>
            <div>
                <label>{this.props.vBoo.getLabel('name')}:</label>
                <Input type="text" name="name" />
                {this.getError('name')}
            </div>
            <div>
                <label>{this.props.vBoo.getLabel('email')}:</label>
                <Input type="text" name="email" value="default@mail.ru" />
                {this.getError('email')}
            </div>
            <div>
                <label>{this.props.vBoo.getLabel('remember')}:</label>
                <InputCheckbox name="remember" value="yes" />
                {this.getError('remember')}
            </div>
            <button onClick={this.sendForm}>
                {this.props.vBoo.isValid() ? 'Можно отправлять': 'Будьте внимательны!!!'}
            </button>
        </Form>
    }
}
export default connect({
    rules: (lang) => {
        let rules =  [
            [
                ['name', 'email'],
                'required',
                {
                    error: '%name% не должно быть пустым'
                }
            ],
            ['email', 'email']
        ];
        rules.push(['remember', lang === 'ru' ? 'required': 'valid']);
        return rules;
    },
    labels: (lang) => ({
        name: 'Имя',
        email: 'Электронная почта',
        remember: 'Запомнить'
    }),
    lang: 'ru'
})(MyForm);

In this example, the remember checkbox in Russian must be set to required , and on others it is always valid valid .

We also passed to connect function labels (lang) , which returns the name of the fields in a readable form.

In the props of your component, there is a function getLabel (name) , which returns the value passed by the function labels, or if there is no such value, it returns the name .

VBoo base components


Form , Input , InputRadio , InputCheckbox , Select , Textarea .

import React, {Component} from'react';
import {connect, Form, Input, Select, InputRadio, InputCheckbox, Textarea} from'react-validation-boo';
classMyFormextendsComponent{
    sendForm = (event) => {
        event.preventDefault();
        if(this.props.vBoo.isValid()) {
            console.log('Получаем введённые значения и отправляем их на сервер', this.props.vBoo.getValues());
        } else {
            console.log('Выведем в консоль ошибки', this.props.vBoo.getErrors());
        }
    };
    getError = (name) => {
        returnthis.props.vBoo.hasError(name) ? <divclassName="error">{this.props.vBoo.getError(name)}</div> : '';
    };
    render() {
        return <Form connect={this.props.vBoo.connect}>
            <div>
                <label>{this.props.vBoo.getLabel('name')}:</label>
                <Input type="text" name="name" />
                {this.getError('name')}
            </div>
            <div>
                <label>{this.props.vBoo.getLabel('email')}:</label>
                <Input type="text" name="email" value="default@mail.ru" />
                {this.getError('email')}
            </div>
            <div>
                <label>{this.props.vBoo.getLabel('gender')}:</label>
                <Select name="gender">
                    <option disabled>Ваш пол</option>
                    <option value="1">Мужской</option>
                    <option value="2">Женский</option>
                </Select>
                {this.getError('gender')}
            </div>
            <div>
                <div>{this.props.vBoo.getLabel('familyStatus')}:</div>
                <div>
                    <InputRadio name="familyStatus" value="1" checked />
                    <label>холост</label>
                </div>
                <div>
                    <InputRadio name="familyStatus" value="2" />
                    <label>сожительство</label>
                </div>
                <div>
                    <InputRadio name="familyStatus" value="3" />
                    <label>брак</label>
                </div>
                {this.getError('familyStatus')}
            </div>
            <div>
                <label>{this.props.vBoo.getLabel('comment')}:</label>
                <Textarea name="comment"></Textarea>
                {this.getError('comment')}
            </div>
            <div>
                <label>{this.props.vBoo.getLabel('remember')}:</label>
                <InputCheckbox name="remember" value="yes" />
                {this.getError('remember')}
            </div>
            <button onClick={this.sendForm}>
                {this.props.vBoo.isValid() ? 'Можно отправлять': 'Будьте внимательны!!!'}
            </button>
        </Form>
    }
}
export default connect({
    rules: () => ([
        [
            ['name', 'email'],
            'required',
            {
                error: '%name% не должно быть пустым'
            }
        ],
        ['email', 'email'],
        [['gender', 'familyStatus', 'comment', 'remember'], 'valid']
    ]),
    labels: () => ({
        name: 'Имя',
        email: 'Электронная почта',
        gender: 'Пол',
        familyStatus: 'Семейное положение',
        comment: 'Комментарий',
        remember: 'Запомнить'
    }),
    lang: 'ru'
})(MyForm);

Validation Rules


Let's take a look at how to write your own validation rules.
In order to write a rule, you must create a class that will be inherited from the validator class .

import {validator} from'react-validation-boo';
classmyValidatorextendsvalidator{
    /**
    * name - имя поля, если есть label то передастся он
    * value - текущее значение поля
    * params - параметры которые были переданны 3-м агрументом в правила валидации(rules)
    */
    validate(name, value, params) {
        let lang = this.getLang();
        let pattern = /^\d+$/;
        if(!pattern.test(value)) {
            let error = params.error || 'Ошибка для поля %name% со значением %value%';
            error = error.replace('%name%', name);
            error = error.replace('%value%', value);
            this.addError(error);
        }
    }
}
exportdefault myValidator;

Now we connect our validator to the form.
import myValidator from'path/myValidator';
// ...exportdefault connect({
    rules: () => ([
        [
            'name',
            'required',
            {
                error: '%name% не должно быть пустым'
            }
        ],
        [
            'name',
            'myValidator',
            {
                error: 'это и будет params.error'
            }
        ]
    ]),
    labels: () => ({
        name: 'Имя'
    }),
    validators: {
        myValidator
    },
    lang: 'ru'
})(MyForm);

In order not to prescribe all your validation rules every time, create a separate file where they will be registered and include its validators: `import 'file-validation'` . And if there are any special rules for this form, then validators: Object.assign ({}, `import 'file-validation'`, {...})

Scenarios


Consider cases when we need to change the validation rules depending on the actions performed on the form.

By default, we have a script called default , in the rules we can write in which scenario to perform this validation.

If the script is not specified, then validation will be performed for all scripts.

rules = () => ([
    [
        'name',
        'required',
        {
            error: '%name% не должно быть пустым'
        }
    ],
    [
        'name',
        'myValidator',
        {
            scenario: ['default', 'scenario1']
        }
    ],
    [
        'email',
        'email',
        {
            scenario: 'scenario1'
        }
    ]
])

Through the props property of our component, functions are passed:

  1. vBoo.setScenario (scenario) - sets the scenario. A scenario can be either a string or an array if we have several scenarios active at once.
  2. vBoo.getScenario () - returns the current script or array of scripts
  3. vBoo.hasScenario (name) - whether the given script is installed now, name is a string

Let's add in our form a scenaries object in which we will store all possible scenarios, true script is active, false is not.

As well as the addScenaries and deleteScenaries functions that will add and delete scripts.

If our “marital status” is chosen as “cohabitation” or “marriage”, then we add a comment field and of course this field should be validated only in this case, the scenario “ scenario-married ”.

If we have the “Advanced” checkbox set, then add additional fields that will become required, the scenario “ scenario-addition ”.

import React, {Component} from'react';
import {connect, Form, Input, Select, InputRadio, InputCheckbox, Textarea} from'react-validation-boo';
classMyFormextendsComponent{
    constructor() {
        super();
        this.scenaries = {
            'scenario-married': false,
            'scenario-addition': false
        }
    }
    changeScenaries(addScenaries = [], deleteScenaries = []) {
        addScenaries.forEach(item =>this.scenaries[item] = true);
        deleteScenaries.forEach(item =>this.scenaries[item] = false);
        let scenario = Object.keys(this.scenaries)
            .reduce((result, item) =>this.scenaries[item]? result.concat(item): result, []);
        this.props.vBoo.setScenario(scenario);
    }
    addScenaries = (m = []) =>this.changeScenaries(m, []);
    deleteScenaries = (m = []) =>this.changeScenaries([], m);
    sendForm = (event) => {
        event.preventDefault();
        if(this.props.vBoo.isValid()) {
            console.log('Получаем введённые значения и отправляем их на сервер', this.props.vBoo.getValues());
        } else {
            console.log('Выведем в консоль ошибки', this.props.vBoo.getErrors());
        }
    };
    getError = (name) => {
        returnthis.props.vBoo.hasError(name) ? <divclassName="error">{this.props.vBoo.getError(name)}</div> : '';
    };
    changeFamilyStatus = (event) => {
        let val = event.target.value;
        if(val !== '1') {
            this.addScenaries(['scenario-married'])
        } else {
            this.deleteScenaries(['scenario-married']);
        }
    };
    changeAddition = (event) => {
        let check = event.target.checked;
        if(check) {
            this.addScenaries(['scenario-addition'])
        } else {
            this.deleteScenaries(['scenario-addition']);
        }
    };
    getCommentContent() {
        if(this.props.vBoo.hasScenario('scenario-married')) {
            return (
                <divkey="comment-content"><label>{this.props.vBoo.getLabel('comment')}:</label><Textareaname="comment"></Textarea>
                    {this.getError('comment')}
                </div>
            );
        }
        return'';
    }
    getAdditionContent() {
        if(this.props.vBoo.hasScenario('scenario-addition')) {
            return (
                <div key="addition-content">
                    <label>{this.props.vBoo.getLabel('place')}:</label>
                    <Input type="text" name="place" />
                    {this.getError('place')}
                </div>
            );
        }
        return '';
    }
    render() {
        return <Form connect={this.props.vBoo.connect}>
            <div>
                <label>{this.props.vBoo.getLabel('name')}:</label>
                <Input type="text" name="name" />
                {this.getError('name')}
            </div>
            <div>
                <label>{this.props.vBoo.getLabel('email')}:</label>
                <Input type="text" name="email" value="default@mail.ru" />
                {this.getError('email')}
            </div>
            <div>
                <label>{this.props.vBoo.getLabel('gender')}:</label>
                <Select name="gender">
                    <option disabled>Ваш пол</option>
                    <option value="1">Мужской</option>
                    <option value="2">Женский</option>
                </Select>
                {this.getError('gender')}
            </div>
            <div>
                <div>{this.props.vBoo.getLabel('familyStatus')}:</div>
                <div>
                    <InputRadio name="familyStatus" value="1" checked onChange={this.changeFamilyStatus} />
                    <label>холост</label>
                </div>
                <div>
                    <InputRadio name="familyStatus" value="2" onChange={this.changeFamilyStatus} />
                    <label>сожительство</label>
                </div>
                <div>
                    <InputRadio name="familyStatus" value="3" onChange={this.changeFamilyStatus} />
                    <label>брак</label>
                </div>
                {this.getError('familyStatus')}
            </div>
            {this.getCommentContent()}
            <div>
                <label>{this.props.vBoo.getLabel('addition')}:</label>
                <InputCheckbox name="addition" value="yes" onChange={this.changeAddition} />
                {this.getError('addition')}
            </div>
            {this.getAdditionContent()}
            <button onClick={this.sendForm}>
                {this.props.vBoo.isValid() ? 'Можно отправлять': 'Будьте внимательны!!!'}
            </button>
        </Form>
    }
}
export default connect({
    rules: () => ([
        [
            ['name', 'gender', 'familyStatus', 'email'],
            'required',
            {
                error: '%name% не должно быть пустым'
            }
        ],
        ['email', 'email'],
        [
            'comment',
            'required',
            {
                scenario: 'scenario-married'
            }
        ],
        ['addition', 'valid'],
        [
            'place',
            'required',
            {
                scenario: 'scenario-addition'
            }
        ],
    ]),
    labels: () => ({
        name: 'Имя',
        email: 'Электронная почта',
        gender: 'Пол',
        familyStatus: 'Семейное положение',
        comment: 'Комментарий',
        addition: 'Дополнительно',
        place: 'Место'
    }),
    lang: 'ru'
})(MyForm);

In order not to make the article very large, I will continue in the next one, where I will write how to create my components (for example, a calendar or inputSearch) and validate them, how to associate with redux and others.

Also popular now: