Pull to refresh

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

Reading time 3 min
Views 5.4K
Пару лет назад я увидел на Хабре статью про 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)
Tags:
Hubs:
+15
Comments 8
Comments Comments 8

Articles