├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── Readme.md
├── examples
└── example.gif
├── package-lock.json
├── package.json
├── src
├── Form.js
├── Input.js
├── TextValidator.jsx
├── ValidationRules.jsx
├── index.js
└── utils.js
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/**
2 | **/__test__/**
3 | build/**
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser" : "babel-eslint",
3 | "plugins": [
4 | "import"
5 | ],
6 | "extends" : ["airbnb"],
7 | "rules": {
8 | // Soft some rules.
9 | "camelcase": "off",
10 | "class-methods-use-this": "off",
11 | "default-case": 0, // Required default case is nonsense.
12 | "indent": [2, 4, { 'SwitchCase': 1, 'VariableDeclarator': 1 }],
13 | "linebreak-style": "off",
14 | "max-len": "off",
15 | "new-cap": [2, {"capIsNew": false, "newIsCap": true}], // For Record() etc.
16 | "newline-per-chained-call": 0,
17 | "no-cond-assign": "off",
18 | "no-floating-decimal": 0, // .5 is just fine.
19 | "no-nested-ternary": 0, // It's nice for JSX.
20 | "no-param-reassign": 0, // We love param reassignment. Naming is hard.
21 | "no-plusplus": 0,
22 | "no-prototype-builtins": 0,
23 | "no-shadow": 0, // Shadowing is a nice language feature. Naming is hard.
24 | "react/no-string-refs": 0,
25 | "no-underscore-dangle": "off",
26 | // eslint-plugin-import
27 | "import/no-unresolved": [2, {"commonjs": true}],
28 | "import/no-extraneous-dependencies": 0,
29 | "import/named": 2,
30 | "import/default": 2,
31 | "import/namespace": 2,
32 | "import/export": 2,
33 | "func-names": ["error", "as-needed"],
34 | // Overide Stateless
35 | "react/prefer-stateless-function": 0,
36 | "react/jsx-indent": [2, 4],
37 | "react/jsx-indent-props": [2, 4],
38 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
39 | "react/no-unused-prop-types": 0,
40 | "react/no-array-index-key": 0,
41 | "react/forbid-prop-types": 0,
42 | "react/require-default-props": 0,
43 | "jsx-a11y/anchor-has-content": 0,
44 | },
45 | "globals": {
46 | "after": false,
47 | "afterEach": false,
48 | "before": false,
49 | "beforeEach": false,
50 | "describe": false,
51 | "it": false,
52 | "require": false,
53 | "window": true,
54 | "localStorage": true,
55 | "document": true,
56 | "navigator": true,
57 | "location": true,
58 | "XMLHttpRequest": true,
59 | "XDomainRequest": true,
60 | "Blob": true,
61 | },
62 | "settings": {
63 | "import/ignore": [
64 | "node_modules",
65 | "\\.json$"
66 | ],
67 | "import/parser": "babel-eslint",
68 | "import/resolve": {
69 | "extensions": [
70 | ".js",
71 | ".jsx",
72 | ".json"
73 | ]
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib/*
2 | etc/*
3 | node_modules
4 | .DS_Store
5 | build/*
6 | npm-debug.log
7 | config.jsx
8 | !config.jsx.dist
9 | .idea
10 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | examples/*
2 | src/*
3 | etc/*
4 | node_modules
5 | .DS_Store
6 | build/*
7 | npm-debug.log
8 | config.jsx
9 | !config.jsx.dist
10 | .idea
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | dist: trusty
3 | language: node_js
4 | node_js:
5 | - 6
6 | rvm:
7 | - 2.2.3
8 | script: "npm run lint"
9 | cache:
10 | directories:
11 | - node_modules
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 NewOldMax
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | ## Validation component for react-native forms
2 |
3 | [](https://opensource.org/licenses/MIT)
4 | [](https://badge.fury.io/js/react-native-validator-form)
5 | [](https://travis-ci.org/NewOldMax/react-native-validator-form)
6 |
7 | Simple form validation component for React-Native inspired by [formsy-react](https://github.com/christianalfoni/formsy-react).
8 | Web version: [react-material-ui-form-validator](https://github.com/NewOldMax/react-material-ui-form-validator)
9 |
10 | Unfortunately I don't have Mac, so this library is tested only on Android.
11 |
12 | Supported types:
13 | + TextInput
14 |
15 | Default validation rules:
16 | + matchRegexp
17 | + isEmail
18 | + isEmpty
19 | + required
20 | + trim
21 | + isNumber
22 | + isFloat
23 | + isPositive
24 | + minNumber
25 | + maxNumber
26 | + minFloat
27 | + maxFloat
28 | + minStringLength
29 | + maxStringLength
30 | + isString
31 |
32 | Some rules can accept extra parameter, example:
33 | ````javascript
34 |
38 | ````
39 |
40 | ### Example
41 |
42 |
43 |
44 | ### Usage
45 |
46 | ````javascript
47 |
48 | import React from 'react';
49 | import { Button } from 'react-native';
50 | import { Form, TextValidator } from 'react-native-validator-form';
51 |
52 | class MyForm extends React.Component {
53 | state = {
54 | email: '',
55 | }
56 |
57 | handleChange = (email) => {
58 | this.setState({ email });
59 | }
60 |
61 | submit = () => {
62 | // your submit logic
63 | }
64 |
65 | handleSubmit = () => {
66 | this.refs.form.submit();
67 | }
68 |
69 | render() {
70 | const { email } = this.state;
71 | return (
72 |
92 | );
93 | }
94 | }
95 |
96 | ````
97 |
98 | #### You can add your own rules
99 | ````javascript
100 | Form.addValidationRule('isPasswordMatch', (value) => {
101 | if (value !== this.state.user.password) {
102 | return false;
103 | }
104 | return true;
105 | });
106 | ````
107 | And remove them
108 | ````javascript
109 | componentWillUnmount() {
110 | Form.removeValidationRule('isPasswordMatch');
111 | }
112 | ````
113 | Usage
114 | ````javascript
115 | import React from 'react';
116 | import { Button } from 'react-native';
117 | import { Form, TextValidator } from 'react-native-validator-form';
118 |
119 | class ResetPasswordForm extends React.Component {
120 | state = {
121 | user: {},
122 | }
123 |
124 | componentWillMount() {
125 | // custom rule will have name 'isPasswordMatch'
126 | Form.addValidationRule('isPasswordMatch', (value) => {
127 | if (value !== this.state.user.password) {
128 | return false;
129 | }
130 | return true;
131 | });
132 | }
133 |
134 | componentWillUnmount() {
135 | Form.removeValidationRule('isPasswordMatch');
136 | }
137 |
138 | handlePassword = (event) => {
139 | const { user } = this.state;
140 | user.password = event.nativeEvent.text;
141 | this.setState({ user });
142 | }
143 |
144 | handleRepeatPassword = (event) => {
145 | const { user } = this.state;
146 | user.repeatPassword = event.nativeEvent.text;
147 | this.setState({ user });
148 | }
149 |
150 | submit = () => {
151 | // your submit logic
152 | }
153 |
154 | handleSubmit = () => {
155 | this.refs.form.submit();
156 | }
157 |
158 | render() {
159 | const { user } = this.state;
160 | return (
161 |
190 | );
191 | }
192 | }
193 | ````
194 |
195 | ##### [Advanced usage](https://github.com/NewOldMax/react-native-validator-form/wiki)
196 |
197 | ### API
198 |
199 | #### Form
200 |
201 | + Props
202 |
203 | | Prop | Required | Type | Default value | Description |
204 | |-----------------|----------|----------|---------------|------------------------------------------------------------------------------------------------------------------------------|
205 | | onSubmit | true | function | | Callback for form that fires when all validations are passed |
206 | | instantValidate | false | bool | true | If true, form will be validated after each field change. If false, form will be validated only after clicking submit button. |
207 | | onError | false | function | | Callback for form that fires when some of validations are not passed. It will return array of elements which not valid. |
208 | | debounceTime | false | number | 0 | Debounce time for validation i.e. your validation will run after `debounceTime` ms when you stop changing your input |
209 |
210 | + Methods
211 |
212 | | Name | Params | Return | Descriptipon |
213 | |------------------|--------|--------|----------------------------------------------------|
214 | | resetValidations | | | Reset validation messages for all validated inputs |
215 | | isFormValid | dryRun: bool (default true) | Promise | Get form validation state in a Promise (`true` if whole form is valid). Run with `dryRun = false` to show validation errors on form |
216 |
217 | #### All validated fields (Input)
218 |
219 | + Props
220 |
221 | | Prop | Required | Type | Default value | Description |
222 | |-----------------|----------|----------|---------------|----------------------------------------------------------------------------------------|
223 | | validators | false | array | | Array of validators. See list of default validators above. |
224 | | errorMessages | false | array | | Array of error messages. Order of messages should be the same as `validators` prop. |
225 | | name | true | string | | Name of input |
226 | | errorStyle | false | object | { container: { top: 0, left: 0, position: 'absolute' }, text: { color: 'red' }, underlineValidColor: 'gray', underlineInvalidColor: 'red' } } | Error styles |
227 | | validatorListener | false | function | | It triggers after each validation. It will return `true` or `false` |
228 | | withRequiredValidator | false | bool | | Allow to use `required` validator in any validation trigger, not only form submit |
229 |
230 | + Methods
231 |
232 | | Name | Params | Return | Descriptipon |
233 | |------------------|--------|--------|----------------------------------------------------|
234 | | getErrorMessage | | | Get error validation message |
235 | | validate | value: any, includeRequired: bool | | Run validation for current component |
236 | | isValid | | bool | Return current validation state |
237 | | makeInvalid | | | Set invalid validation state |
238 | | makeValid | | | Set valid validation state |
239 |
240 | ### Contributing
241 |
242 | This component covers all my needs, but feel free to contribute.
243 |
--------------------------------------------------------------------------------
/examples/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewOldMax/react-native-validator-form/13b6c9b16f4ea3c51ccb4b48583662fd3d4f2e58/examples/example.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-validator-form",
3 | "version": "1.0.0",
4 | "description": "Simple validator for react-native forms.",
5 | "main": "./lib/index.js",
6 | "scripts": {
7 | "build": "babel ./src -d ./lib --presets=react-native",
8 | "prepublish": "npm run build",
9 | "lint": "eslint src/**"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/NewOldMax/react-native-validator-form.git"
14 | },
15 | "keywords": [
16 | "react",
17 | "react native",
18 | "form",
19 | "validation"
20 | ],
21 | "author": "NewOldMax",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/NewOldMax/react-native-validator-form/issues"
25 | },
26 | "homepage": "https://github.com/NewOldMax/react-native-validator-form#readme",
27 | "devDependencies": {
28 | "babel-cli": "^6.3.17",
29 | "babel-core": "^6.5.1",
30 | "babel-eslint": "^7.1.1",
31 | "babel-jest": "17.0.2",
32 | "babel-loader": "^6.2.2",
33 | "babel-polyfill": "^6.5.0",
34 | "babel-preset-react-native": "1.9.0",
35 | "eslint": "^4.18.2",
36 | "eslint-config-airbnb": "^13.0.0",
37 | "eslint-plugin-import": "^2.2.0",
38 | "eslint-plugin-jsx-a11y": "2.2.3",
39 | "eslint-plugin-react": "^6.8.0",
40 | "jest": "17.0.3",
41 | "jest-react-native": "17.0.3",
42 | "key-mirror": "^1.0.0",
43 | "keymirror": "^0.1.1",
44 | "object-assign": "^2.0.0",
45 | "react-test-renderer": "15.3.2"
46 | },
47 | "dependencies": {
48 | "react-lifecycles-compat": "^3.0.4"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Form.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 | import { View } from 'react-native';
5 | /* eslint-enable */
6 | import Rules from './ValidationRules';
7 |
8 | class Form extends React.Component {
9 |
10 | static getValidator = (validator, value, includeRequired) => {
11 | let result = true;
12 | let name = validator;
13 | if (name !== 'required' || includeRequired) {
14 | let extra;
15 | const splitIdx = validator.indexOf(':');
16 | if (splitIdx !== -1) {
17 | name = validator.substring(0, splitIdx);
18 | extra = validator.substring(splitIdx + 1);
19 | }
20 | result = Rules[name](value, extra);
21 | }
22 | return result;
23 | }
24 |
25 | getChildContext = () => ({
26 | form: {
27 | attachToForm: this.attachToForm,
28 | detachFromForm: this.detachFromForm,
29 | instantValidate: this.instantValidate,
30 | debounceTime: this.debounceTime,
31 | },
32 | })
33 |
34 | instantValidate = this.props.instantValidate !== undefined ? this.props.instantValidate : true
35 | debounceTime = this.props.debounceTime
36 | childs = []
37 | errors = []
38 |
39 | attachToForm = (component) => {
40 | if (this.childs.indexOf(component) === -1) {
41 | this.childs.push(component);
42 | }
43 | }
44 |
45 | detachFromForm = (component) => {
46 | const componentPos = this.childs.indexOf(component);
47 | if (componentPos !== -1) {
48 | this.childs = this.childs.slice(0, componentPos)
49 | .concat(this.childs.slice(componentPos + 1));
50 | }
51 | }
52 |
53 | submit = (event) => {
54 | if (event) {
55 | event.preventDefault();
56 | event.persist();
57 | }
58 | this.errors = [];
59 | this.walk(this.childs).then((result) => {
60 | if (this.errors.length) {
61 | this.props.onError(this.errors);
62 | }
63 | if (result) {
64 | this.props.onSubmit(event);
65 | }
66 | return result;
67 | });
68 | }
69 |
70 | walk = (children, dryRun) => {
71 | const self = this;
72 | return new Promise((resolve) => {
73 | let result = true;
74 | if (Array.isArray(children)) {
75 | Promise.all(children.map(input => self.checkInput(input, dryRun))).then((data) => {
76 | data.forEach((item) => {
77 | if (!item) {
78 | result = false;
79 | }
80 | });
81 | resolve(result);
82 | });
83 | } else {
84 | self.walk([children], dryRun).then(result => resolve(result));
85 | }
86 | });
87 | }
88 |
89 | checkInput = (input, dryRun) => (
90 | new Promise((resolve) => {
91 | let result = true;
92 | const validators = input.props.validators;
93 | if (validators) {
94 | this.validate(input, true, dryRun).then((data) => {
95 | if (!data) {
96 | result = false;
97 | }
98 | resolve(result);
99 | });
100 | } else {
101 | resolve(result);
102 | }
103 | })
104 | )
105 |
106 | validate = (input, includeRequired, dryRun) => (
107 | new Promise((resolve) => {
108 | const { value, validators } = input.props;
109 | const result = [];
110 | let valid = true;
111 | const validations = Promise.all(
112 | validators.map(validator => (
113 | Promise.all([
114 | this.constructor.getValidator(validator, value, includeRequired),
115 | ]).then((data) => {
116 | result.push({ input, result: data && data[0] });
117 | input.validate(input.props.value, true, dryRun);
118 | })
119 | )),
120 | );
121 | validations.then(() => {
122 | result.forEach((item) => {
123 | if (!item.result) {
124 | valid = false;
125 | this.errors.push(item.input);
126 | }
127 | });
128 | resolve(valid);
129 | });
130 | })
131 | )
132 |
133 | find = (collection, fn) => {
134 | for (let i = 0, l = collection.length; i < l; i++) {
135 | const item = collection[i];
136 | if (fn(item)) {
137 | return item;
138 | }
139 | }
140 | return null;
141 | }
142 |
143 | resetValidations = () => {
144 | this.childs.forEach((child) => {
145 | child.validateDebounced.cancel();
146 | child.setState({ isValid: true });
147 | });
148 | }
149 |
150 | isFormValid = (dryRun = true) => this.walk(this.childs, dryRun);
151 |
152 | render() {
153 | // eslint-disable-next-line
154 | const { onSubmit, instantValidate, onError, debounceTime, children, ...rest } = this.props;
155 | return (
156 |
157 | {children}
158 |
159 | );
160 | }
161 | }
162 |
163 | Form.addValidationRule = (name, callback) => {
164 | Rules[name] = callback;
165 | };
166 |
167 | Form.removeValidationRule = (name) => {
168 | delete Rules[name];
169 | };
170 |
171 | Form.childContextTypes = {
172 | form: PropTypes.object,
173 | };
174 |
175 | Form.propTypes = {
176 | onSubmit: PropTypes.func.isRequired,
177 | instantValidate: PropTypes.bool,
178 | children: PropTypes.node,
179 | onError: PropTypes.func,
180 | debounceTime: PropTypes.number,
181 | };
182 |
183 | Form.defaultProps = {
184 | onError: () => {},
185 | debounceTime: 0,
186 | };
187 |
188 | export default Form;
189 |
--------------------------------------------------------------------------------
/src/Input.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 | /* eslint-enable */
5 | import { polyfill } from 'react-lifecycles-compat';
6 | import Form from './Form';
7 | import { debounce } from './utils';
8 |
9 | class Input extends React.Component {
10 |
11 | static getDerivedStateFromProps(nextProps, prevState) {
12 | if (nextProps.validators && nextProps.errorMessages &&
13 | (
14 | prevState.validators !== nextProps.validators ||
15 | prevState.errorMessages !== nextProps.errorMessages
16 | )
17 | ) {
18 | return {
19 | value: nextProps.value,
20 | validators: nextProps.validators,
21 | errorMessages: nextProps.errorMessages,
22 | };
23 | }
24 |
25 | return {
26 | value: nextProps.value,
27 | };
28 | }
29 |
30 | state = {
31 | isValid: true,
32 | value: this.props.value,
33 | errorMessages: this.props.errorMessages,
34 | validators: this.props.validators,
35 | }
36 |
37 | componentDidMount() {
38 | this.configure();
39 | }
40 |
41 | shouldComponentUpdate(nextProps, nextState) {
42 | return this.state !== nextState || this.props !== nextProps;
43 | }
44 |
45 | componentDidUpdate(prevProps, prevState) {
46 | if (this.instantValidate && this.props.value !== prevState.value) {
47 | this.validateDebounced(this.props.value, this.props.withRequiredValidator);
48 | }
49 | }
50 |
51 | componentWillUnmount() {
52 | this.context.form.detachFromForm(this);
53 | this.validateDebounced.cancel();
54 | }
55 |
56 | getErrorMessage = () => {
57 | const { errorMessages } = this.state;
58 | const type = typeof errorMessages;
59 |
60 | if (type === 'string') {
61 | return errorMessages;
62 | } else if (type === 'object') {
63 | if (this.invalid.length > 0) {
64 | return errorMessages[this.invalid[0]];
65 | }
66 | }
67 | // eslint-disable-next-line
68 | console.log('unknown errorMessages type', errorMessages);
69 | return true;
70 | }
71 |
72 | instantValidate = true
73 | invalid = []
74 |
75 | configure = () => {
76 | this.context.form.attachToForm(this);
77 | this.instantValidate = this.context.form.instantValidate;
78 | this.debounceTime = this.context.form.debounceTime;
79 | this.validateDebounced = debounce(this.validate, this.debounceTime);
80 | }
81 |
82 | validate = (value, includeRequired = false, dryRun = false) => {
83 | const validations = Promise.all(
84 | this.state.validators.map(validator => Form.getValidator(validator, value, includeRequired)),
85 | );
86 |
87 | validations.then((results) => {
88 | this.invalid = [];
89 | let valid = true;
90 | results.forEach((result, key) => {
91 | if (!result) {
92 | valid = false;
93 | this.invalid.push(key);
94 | }
95 | });
96 | if (!dryRun) {
97 | this.setState({ isValid: valid }, () => {
98 | this.props.validatorListener(this.state.isValid);
99 | });
100 | }
101 | });
102 | }
103 |
104 | isValid = () => this.state.isValid;
105 |
106 | makeInvalid = () => {
107 | this.setState({ isValid: false });
108 | }
109 |
110 | makeValid = () => {
111 | this.setState({ isValid: true });
112 | }
113 | }
114 |
115 | Input.contextTypes = {
116 | form: PropTypes.object,
117 | };
118 |
119 | Input.propTypes = {
120 | errorStyle: PropTypes.shape({
121 | container: PropTypes.shape({
122 | top: PropTypes.number,
123 | left: PropTypes.number,
124 | position: PropTypes.string,
125 | }),
126 | text: PropTypes.shape({}),
127 | underlineValidColor: PropTypes.string,
128 | underlineInvalidColor: PropTypes.string,
129 | }),
130 | errorMessages: PropTypes.oneOfType([
131 | PropTypes.array,
132 | PropTypes.string,
133 | ]),
134 | validators: PropTypes.array,
135 | value: PropTypes.any,
136 | validatorListener: PropTypes.func,
137 | withRequiredValidator: PropTypes.bool,
138 | };
139 |
140 | Input.defaultProps = {
141 | errorStyle: {
142 | container: {
143 | top: 0,
144 | left: 0,
145 | position: 'absolute',
146 | },
147 | text: {
148 | color: 'red',
149 | },
150 | underlineValidColor: 'gray',
151 | underlineInvalidColor: 'red',
152 | },
153 | errorMessages: 'error',
154 | validators: [],
155 | validatorListener: () => {},
156 | };
157 |
158 | polyfill(Input);
159 |
160 | export default Input;
161 |
--------------------------------------------------------------------------------
/src/TextValidator.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import React from 'react';
3 | import { TextInput, View, Text } from 'react-native';
4 | /* eslint-enable */
5 | import Input from './Input';
6 |
7 | export default class TextValidator extends Input {
8 |
9 | render() {
10 | /* eslint-disable no-unused-vars */
11 | const {
12 | errorMessage,
13 | validators,
14 | requiredError,
15 | underlineColorAndroid,
16 | errorStyle,
17 | validatorListener,
18 | ...rest
19 | } = this.props;
20 | let underlineColor = underlineColorAndroid;
21 | if (!underlineColor) {
22 | underlineColor = !this.state.isValid ? errorStyle.underlineInvalidColor : errorStyle.underlineValidColor;
23 | }
24 | return (
25 |
26 | { this.input = r; }}
30 | />
31 | {this.errorText()}
32 |
33 | );
34 | }
35 |
36 | errorText() {
37 | const { errorStyle } = this.props;
38 | const { isValid } = this.state;
39 |
40 | if (isValid) {
41 | return null;
42 | }
43 |
44 | return (
45 |
46 | {this.getErrorMessage()}
47 |
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/ValidationRules.jsx:
--------------------------------------------------------------------------------
1 | const isExisty = function (value) {
2 | return value !== null && value !== undefined;
3 | };
4 |
5 | const isEmpty = function (value) {
6 | if (value instanceof Array) {
7 | return value.length === 0;
8 | }
9 | return value === '' || !isExisty(value);
10 | };
11 |
12 | const isEmptyTrimed = function (value) {
13 | if (typeof value === 'string') {
14 | return value.trim() === '';
15 | }
16 | return true;
17 | };
18 |
19 | const validations = {
20 | matchRegexp: (value, regexp) => {
21 | const validationRegexp = (regexp instanceof RegExp ? regexp : (new RegExp(regexp)));
22 | return (isEmpty(value) || validationRegexp.test(value));
23 | },
24 |
25 | // eslint-disable-next-line
26 | isEmail: value => validations.matchRegexp(value, /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i),
27 |
28 | isEmpty: value => isEmpty(value),
29 |
30 | required: value => !isEmpty(value),
31 |
32 | trim: value => !isEmptyTrimed(value),
33 |
34 | isNumber: value => validations.matchRegexp(value, /^-?[0-9]\d*(\d+)?$/i),
35 |
36 | isFloat: value => validations.matchRegexp(value, /^(?:-?[1-9]\d*|-?0)?(?:\.\d+)?$/i),
37 |
38 | isPositive: (value) => {
39 | if (isExisty(value)) {
40 | return (validations.isNumber(value) || validations.isFloat(value)) && value >= 0;
41 | }
42 | return true;
43 | },
44 |
45 | maxNumber: (value, max) => isEmpty(value) || parseInt(value, 10) <= parseInt(max, 10),
46 |
47 | minNumber: (value, min) => isEmpty(value) || parseInt(value, 10) >= parseInt(min, 10),
48 |
49 | maxFloat: (value, max) => isEmpty(value) || parseFloat(value) <= parseFloat(max),
50 |
51 | minFloat: (value, min) => isEmpty(value) || parseFloat(value) >= parseFloat(min),
52 |
53 | isString: value => !isEmpty(value) || typeof value === 'string' || value instanceof String,
54 | minStringLength: (value, length) => validations.isString(value) && value.length >= length,
55 | maxStringLength: (value, length) => validations.isString(value) && value.length <= length,
56 | };
57 |
58 | module.exports = validations;
59 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import TextValidator from './TextValidator';
2 | import Form from './Form';
3 | import Input from './Input';
4 | import ValidationRules from './ValidationRules';
5 |
6 | exports.TextValidator = TextValidator;
7 | exports.Form = Form;
8 | exports.Input = Input;
9 | exports.ValidationRules = ValidationRules;
10 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | const debounce = (func, wait, immediate) => {
2 | let timeout;
3 | function cancel() {
4 | if (timeout !== undefined) {
5 | clearTimeout(timeout);
6 | }
7 | }
8 | const debounced = function debounced(...args) {
9 | const context = this;
10 | const later = function delayed() {
11 | timeout = null;
12 | if (!immediate) {
13 | func.apply(context, args);
14 | }
15 | };
16 | const callNow = immediate && !timeout;
17 | clearTimeout(timeout);
18 | timeout = setTimeout(later, wait);
19 | if (callNow) {
20 | func.apply(context, args);
21 | }
22 | };
23 | debounced.cancel = cancel;
24 | return debounced;
25 | };
26 |
27 | export {
28 | // eslint-disable-next-line
29 | debounce,
30 | };
31 |
--------------------------------------------------------------------------------