Валидация React компонентов с помощью Livr.js

    Пару лет назад я увидел на Хабре статью про LIVR и с тех пор использую библиотеку на всех проектах. С переходом на React я адаптировал для валидации ее же, т.к. существующие решения не предлагали гибкости которой мне хотелось. Свое решение я уже использую на двух проектах и решил выложить в npm — может кому-то еще оно покажетсяя удобным.
    Пакет называется react-livr-validation.

    Пример базового использования:

    import React from 'react';
    import Validation, {DisabledOnErrors, ValidationInput} from 'react-livr-validation';
    
    const schema = {
        login: ['required', 'not_empty'],
        password: ['required', 'not_empty']
    };
    
    const data = {
        login: '',
        password: ''
    };
    
    export default function() {
        return (
            <Validation
                data={data}
                schema={schema}
            >
                <form>
                    <ValidationInput name="login" >
                        <input name="login" />
                    </ValidationInput>
                    <ValidationInput name="password" >
                        <input name="password" type="password" />
                    </ValidationInput>
                    <DisabledOnErrors>
                        <input type="submit" />
                    </DisabledOnErrors>
                </form>
            </Validation>
        );   
    }
    

    Компонент принимает валидационную схему и первоначальные данные(если данные не валидны, кнопка submit сразу будет неактивна), так же можно передать custom rules и aliased rules:

    const customRules = {
        alpha_chars: function() {
            return function(value) {
                if (typeof value === 'string') {
                    if (!/[a-z,A-Z]+/.test(value)) {
                        return 'WRONG_FORMAT';
                    }
                }
            };
        }
    };
    const aliasedRules = [
        {
            name: 'strong_password',
            rules: { min_length: 6 },
            error: 'TOO_SHORT'
        }
    ];
    <Validation
          data={data}
          schema={schema}
          rules={customRules}
          aliasedRules={aliasedRules}
    >
          // ... form
    </Validation>
    

    Обертка ValidationInput добавляет в инпут свои обработчики событий, на которые будет происходить валидация. По умолчанию это события change, blur, keyup.

    <ValidationInput name="password" valicateOnEvents={['change', 'blur', 'keyUp']}  >
            <input name="password" type="password" />
    </ValidationInput>
    

    Есть возможность реализовать свою обертку — пакет экпортит HOC, который прокидывает в пропсы api

    // @flow
    
    import React, {Component} from 'react'
    import {ValidationComponent} from 'react-livr-validation'
    import get from 'lodash/get'
    import noop from 'lodash/noop'
    import compose from 'ramda/src/compose'
    import styled from 'styled-components'
    
    type DataChunk = {
        name: string,
        value: any
    }
    
    type State = {
        touched: boolean
    }
    
    type Props = {
        // will be passed by HOC
        setData: (data: DataChunk) => void,
        getError: (name: string) => ?string,
        getErrors: () => Object,
        className: string, // for the error block
        style: Object // for the error block
        errorCodes: Object,
        
        name: string,
        field: string
    }
    
    class NestedError extends Component {
        props: Props;
        
        isTouched() {
            const {children} = this.props;
            return get(children, 'props.value')
        }
        
        state: State = {
            touched: this.isTouched()
        }
        
        setTouched() {
            this.setState({
                touched: true
            })
        }
        
        cloneElement() {
            const {children} = this.props;
            const onBlur = get(children, 'props.onBlur', noop);
            return React.cloneElement(
                children,
                {
                    onBlur: compose(this.setTouched, onBlur)
                }
            )
        }
        
        render() {
            const {touched} = this.state;
            const {
                children, 
                field, 
                name, 
                getError,
                errorCodes,
                style,
                className
            } = this.props;
            const errors = getErrors();
            const error = get(errors, `${field}`.${name});
            return (
                <div>
                    {touched ? children : this.cloneElement()}
                    {error &&
                        <Error
                            className={className}
                            style={style}
                        >
                            {errorCodes[error] || error}
                        </Error>
                    }
                </div>
            );
        }
    }
    
    const Error = styled.div`
        color: red;
    `;
    
    export default ValidationComponent(NestedError)
    
    • +15
    • 4.5k
    • 8
    Share post

    Comments 8

      –1
      Вы </Error не закрыли в последнем примере
        0
        поправил
        0
        как это будет работать с react-form? есть примеры?
          0
          ваша концепция LIVIR очень похожа на то, что я хотел от валидации на JS с год назад. я ничего стоящего не нашёл тогда и в итоге написал свой велосипед который очень похож на ваш, но работает только на js и обладает (пока) меньшим функционалом.

          вопрос: можно ли в LIVIR сделать так чтобы поле валидировалось только при определённых условиях? например, валидировать email только если пользователь подписался на рассылку. в моём велосипеде это делается как-то так:

          const {
            validate,
            util: { when },
            rules: { isBoolean, isEmail }
          } = require('yeval');
          
          const optedInForNewsletter = (value, data) => data.optedInForNewsletter === true;
          
          validate(
            // declare rules as first argument
            {
              optedInForNewsletter: isBoolean,
              email: when(optedInForNewsletter, isEmail),
            },
            // pass data as second argument
            { optedInForNewsletter: true }
          )
            .then(errors => {
              console.log(errors); // { email: 'Must be a valid email address' }
            });
          
          
            +1

            У нас в проектах мы сделали правило required_if


            И пишем так:


            {    
                optedInForNewsletter: {one_of: [1, 0]},
                email:  [{required_if: 'optedInForNewsletter'}, 'email'],
            }
              0
              Как вариант можно еще динамически менять правила валидации, если правил много и применять надо только при условии.
              import React, {Component} from 'react';
              import Validation, {DisabledOnErrors, ValidationInput} from 'react-livr-validation';
              
              const data = {
                  login: '',
                  email: ''
              };
              
              class App extends Component {
                  state = {
                      withEmail: false
                  };
              
                  toggleWithEmail = ({target: {checked}}) => {
                      this.setState({
                          withEmail: checked
                      })
                  };
              
                  render() {
                      const {withEmail} = this.state;
                      const schema = {
                          login: ['required', 'not_empty'],
                          email: withEmail ? ['required', 'not_empty', 'email'] : []
                      };
                      return (
                          <div>
                              <Validation
                                  data={data}
                                  schema={schema}
                              >
                                  <form>
                                      <ValidationInput name="login">
                                          <input name="login"/>
                                      </ValidationInput>
                                      <ValidationInput name="email">
                                          <input name="email" type="email"/>
                                      </ValidationInput>
                                      <div>
                                          <label htmlFor="subscribe">subscribe</label>
                                          <input
                                              id="subscribe"
                                              type="checkbox"
                                              onChange={this.toggleWithEmail}
                                          />
                                      </div>
                                      <DisabledOnErrors>
                                          <input type="submit"/>
                                      </DisabledOnErrors>
                                  </form>
                              </Validation>
                          </div>
                      );
                  }
              }
              
                0

                Вот она валидация вашей мечты — https://github.com/jquense/react-formal

                0

                Возможно будет тоже полезно. Для JavaScript, кроме основных правил, есть отдельный пакет с дополнительными правилами — https://www.npmjs.com/package/livr-extra-rules и он тоже zero dependencies :)

                Only users with full accounts can post comments. Log in, please.