Пару лет назад я увидел на Хабре статью про 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)

Комментарии (7)


  1. argonavtt
    10.09.2017 16:19
    -1

    Вы </Error не закрыли в последнем примере


    1. one_more Автор
      10.09.2017 16:23

      поправил


  1. Holms
    10.09.2017 21:36

    как это будет работать с react-form? есть примеры?


  1. turbo_exe
    10.09.2017 22:30

    ваша концепция 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. koorchik
      11.09.2017 10:14
      +1

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


      И пишем так:


      {    
          optedInForNewsletter: {one_of: [1, 0]},
          email:  [{required_if: 'optedInForNewsletter'}, 'email'],
      }


    1. one_more Автор
      11.09.2017 11:31

      Как вариант можно еще динамически менять правила валидации, если правил много и применять надо только при условии.

      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>
              );
          }
      }
      


    1. stardust_kid
      11.09.2017 11:38

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