├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── README.md
├── __tests__
├── functional-component.test.js
└── validation-component.test.js
├── babel.config.js
├── examples
├── FormTest.js
└── FunctionalFormTest.js
├── package-lock.json
├── package.json
└── src
├── ValidationComponent.js
├── index.js
├── messages
└── defaultMessages.js
├── rules
└── defaultRules.js
└── use-validation.js
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | pull_request: ~
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | strategy:
15 | matrix:
16 | node-version: [14.x]
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Use Node.js ${{ matrix.node-version }}
22 | uses: actions/setup-node@v2
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 | registry-url: 'https://registry.npmjs.org'
26 |
27 | - run: npm ci
28 |
29 | - run: npm test
30 |
31 | - name: Conventional Changelog Action
32 | if: ${{ github.ref == 'refs/heads/master' }}
33 | uses: TriPSs/conventional-changelog-action@v3
34 | with:
35 | github-token: ${{ secrets.BUMP_VERSION_TOKEN }}
36 |
37 | - name : Publish library to npm registry
38 | if: ${{ github.ref == 'refs/heads/master' }}
39 | run: npm publish
40 | env:
41 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | project.xcworkspace
24 |
25 | # Android/IJ
26 | #
27 | .idea
28 | .gradle
29 | local.properties
30 |
31 | # node.js
32 | #
33 | node_modules/
34 | npm-debug.log
35 |
36 | # BUCK
37 | buck-out/
38 | \.buckd/
39 | android/app/libs
40 | android/keystores/debug.keystore
41 |
42 | /coverage/
43 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "singleQuote": true,
4 | "useTabs": false,
5 | "tabWidth": 2,
6 | "semi": true,
7 | "bracketSpacing": true,
8 | "trailingComma": "none"
9 | }
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # [0.5.0](https://github.com/perscrew/react-native-form-validator/compare/v0.3.2...v0.5.0) (2021-09-08)
2 |
3 |
4 | ### Features
5 |
6 | * **deps:** update dependencies ([51aa614](https://github.com/perscrew/react-native-form-validator/commit/51aa614642f2b4d1e4d619dbe51b5c9176ca288d))
7 |
8 |
9 |
10 | ## [0.3.2](https://github.com/perscrew/react-native-form-validator/compare/v0.3.1...v0.3.2) (2019-10-25)
11 |
12 |
13 | ### Features
14 |
15 | * **release:** v0.3.2 ([a904b9f](https://github.com/perscrew/react-native-form-validator/commit/a904b9fa8d94600a1218eb21e26693a12a3f5a80))
16 |
17 |
18 |
19 | ## [0.3.1](https://github.com/perscrew/react-native-form-validator/compare/v0.2.0...v0.3.1) (2019-10-17)
20 |
21 |
22 | ### Features
23 |
24 | * **release:** v0.3.1 ([4cd370d](https://github.com/perscrew/react-native-form-validator/commit/4cd370d8dccc3f5441a6ac9856a0fd55f15bbc86))
25 |
26 |
27 |
28 | # [0.2.0](https://github.com/perscrew/react-native-form-validator/compare/v0.1.3...v0.2.0) (2017-12-11)
29 |
30 |
31 |
32 | ## [0.1.3](https://github.com/perscrew/react-native-form-validator/compare/v0.1.2...v0.1.3) (2017-03-10)
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## ** ⚠️ THIS LIBRARY HAS BEEN REWRITTEN WITH TYPESCRIPT AND HAS MOVED TO [https://github.com/perscrew/react-simple-form-validator](https://github.com/perscrew/react-simple-form-validator) ⚠️ **
2 |
3 | # React native form validator
4 | [](https://github.com/perscrew/react-native-form-validator/actions/workflows/ci.yml)
5 | [](https://www.npmjs.com/package/react-native-form-validator)
6 | [](https://GitHub.com/Naereen/StrapDown.js/graphs/commit-activity)
7 | [](https://www.javascript.com)
8 |
9 | React native form validator is a simple library to validate your form fields with React Native.
10 | The library is easy to use. You just have to extend the "ValidationComponent" class on your desired React native form component.
11 |
12 | ## 1. Installation
13 | * Run npm install 'react-native-form-validator' to fetch the library :
14 | ```sh
15 | npm install 'react-native-form-validator' --save
16 | ```
17 |
18 | ## 2. Use it in your app
19 | ### For class component:
20 |
21 |
22 | Extend "ValidationComponent" class on a form component :
23 | ```js
24 | import React from 'react';
25 | import ValidationComponent from 'react-native-form-validator';
26 |
27 | export default class MyForm extends ValidationComponent {
28 | ...
29 | }
30 | ```
31 | The ValidationComponent extends the React.Component class.
32 |
33 | To ensure form validation you have to call the "this.validate" method in a custom function.
34 | ```js
35 | constructor(props) {
36 | super(props);
37 | this.state = {name : "My name", email: "titi@gmail.com", number:"56", date: "2017-03-01"};
38 | }
39 |
40 | _onSubmit() {
41 | // Call ValidationComponent validate method
42 | this.validate({
43 | name: {minlength:3, maxlength:7, required: true},
44 | email: {email: true},
45 | number: {numbers: true},
46 | date: {date: 'YYYY-MM-DD'}
47 | });
48 | }
49 | ```
50 | The method arguments should be a representation of the React component state. The first graph level matches with the React state variables.
51 | The second level matches with the existing validation rules.
52 |
53 | You will find bellow the default rules available in the library [defaultRules.js](./defaultRules.js) :
54 |
55 | |Rule|Benefits|
56 | |-------|--------|
57 | |numbers|Check if a state variable is a number.|
58 | |email|Check if a state variable is an email.|
59 | |required|Check if a state variable is not empty.|
60 | |date|Check if a state variable respects the date pattern. Ex: date: 'YYYY-MM-DD'|
61 | |minlength|Check if a state variable is greater than minlength.|
62 | |maxlength|Check if a state variable is lower than maxlength.|
63 | |equalPassword|Check if a state variable is equal to another value (useful for password confirm).|
64 | |hasNumber|Check if a state variable contains a number.|
65 | |hasUpperCase|Check if a state variable contains a upper case letter.|
66 | |hasLowerCase|Check if a state variable contains a lower case letter.|
67 | |hasSpecialCharacter|Check if a state variable contains a special character.|
68 |
69 | You can also override this file via the component React props :
70 | ```js
71 | const rules = {any: /^(.*)$/};
72 |
73 |
74 | ```
75 |
76 |
77 | Once you have extended the class a set of useful methods become avaiblable :
78 |
79 | |Method|Output|Benefits|
80 | |-------|--------|--------|
81 | |this.validate(state_rules)|Boolean|This method ensures form validation within the object passed in argument.The object should be a representation of the React component state. The first graph level matches with the React state variables.The second level matches with the existing validation rules.|
82 | |this.isFormValid()|Boolean|This method indicates if the form is valid and if there are no errors.|
83 | |this.isFieldInError(fieldName)|Boolean|This method indicates if a specific field has an error. The field name will match with your React state|
84 | |this.getErrorMessages(separator)|String|This method returns the different error messages bound to your React state. The argument is optional, by default the separator is a \n. Under the hood a join method is used.|
85 | |this.getErrorsInField(fieldName)|Array|This method returns the error messages bound to the specified field. The field name will match with your React state. It returns an empty array if no error was bound to the field.|
86 |
87 | The library also contains a [defaultMessages.js](./defaultMessages.js) file which includes the errors label for a language locale.
88 | You can override this file via the component React props :
89 | ```js
90 | const messages = {
91 | en: {numbers: "error on numbers !"},
92 | fr: {numbers: "erreur sur les nombres !"}
93 | };
94 |
95 |
96 | ```
97 | You can add custom labels to the state variables, which will be useful if you want to change it's label in the error messages or translate it
98 | to the local language :
99 | ```js
100 | const labels = {
101 | name: 'Name',
102 | email: 'E-mail',
103 | number: 'Phone number'
104 | };
105 |
106 |
107 | ```
108 |
109 | You can also specify the default custom local language in the props :
110 |
111 | ```js
112 |
113 | ```
114 | ### Dynamic validation onChangeText
115 | You can also use dynamic validation by calling validate function on onChangeText event :
116 |
117 | ```js
118 | {
120 | this.setState({ lastName }, () => {
121 | this.validate({
122 | lastName: { required: true },
123 | })
124 | })
125 | }} value={this.state.lastName} style={[styles.input]} />
126 | {this.isFieldInError('lastName') && this.getErrorsInField('lastName').map(errorMessage => {errorMessage})}
127 | ```
128 |
129 | ### For functional component:
130 |
131 | You will use useValidation hook inside your component like this :
132 |
133 | ```js
134 | import { useValidation } from 'react-native-form-validator';
135 | import customValidationMessages from './customValidationMessages';
136 |
137 | const MyFunction = () => {
138 | const [email, setEmail] = useState('');
139 | const [name, setName] = useState('');
140 |
141 | const { validate, getErrorsInField } = useValidation({
142 | state: { email, name },
143 | messages: customValidationMessages,
144 | });
145 |
146 | const _validateForm = () => {
147 | validate({
148 | email: { email: true },
149 | name: { required: true }
150 | })
151 | }
152 | }
153 | ```
154 | You need to pass the state manually to the useValidation hook in state object like above.
155 | You can also pass custom messages, labels, rules, deviceLocale and it returns object with all the methods that available in the class component.
156 |
157 |
158 | ## 3. Complete example
159 |
160 | ### Class component:
161 |
162 | You can find a complete example in the [formTest.js](./test/formTest.js) file :
163 |
164 | ```js
165 | 'use strict';
166 |
167 | import React, {Component} from 'react';
168 | import {View, Text, TextInput, TouchableHighlight} from 'react-native';
169 | import ValidationComponent from '../index';
170 |
171 | export default class FormTest extends ValidationComponent {
172 |
173 | constructor(props) {
174 | super(props);
175 | this.state = {name : "My name", email: "tibtib@gmail.com", number:"56", date: "2017-03-01", newPassword : "", confirmPassword : ""};
176 | }
177 |
178 | _onPressButton() {
179 | // Call ValidationComponent validate method
180 | this.validate({
181 | name: {minlength:3, maxlength:7, required: true},
182 | email: {email: true},
183 | number: {numbers: true},
184 | date: {date: 'YYYY-MM-DD'},
185 | confirmPassword : {equalPassword : this.state.newPassword}
186 | });
187 | }
188 |
189 | render() {
190 | return (
191 |
192 | this.setState({name})} value={this.state.name} />
193 | this.setState({email})} value={this.state.email} />
194 | this.setState({number})} value={this.state.number} />
195 | this.setState({date})} value={this.state.date} />
196 | {this.isFieldInError('date') && this.getErrorsInField('date').map(errorMessage => {errorMessage}) }
197 |
198 | this.setState({newPassword})} value={this.state.newPassword} secureTextEntry={true}/>
199 | this.setState({confirmPassword})} value={this.state.confirmPassword} secureTextEntry={true} />
200 | {this.isFieldInError('confirmPassword') && this.getErrorsInField('confirmPassword').map(errorMessage => {errorMessage}) }
201 |
202 |
203 | Submit
204 |
205 |
206 |
207 | {this.getErrorMessages()}
208 |
209 |
210 | );
211 | }
212 |
213 | }
214 | ```
215 |
216 | ### Function Component:
217 |
218 | ```js
219 | 'use strict';
220 |
221 | import React, { useState } from 'react';
222 | import { View, Text, TextInput, TouchableHighlight } from 'react-native';
223 | import { useValidation } from 'react-native-form-validator';
224 |
225 | const FormTest = () => {
226 | const [name, setName] = useState('My name');
227 | const [email, setEmail] = useState('tibtib@gmail.com');
228 | const [number, setNumber] = useState('56');
229 | const [date, setDate] = useState('2017-03-01');
230 | const [newPassword, setNewPassword] = useState('');
231 | const [confirmPassword, setConfirmPassword] = useState('');
232 |
233 | const { validate, isFieldInError, getErrorsInField, getErrorMessages } =
234 | useValidation({
235 | state: { name, email, number, date, newPassword, confirmPassword },
236 | });
237 |
238 | const _onPressButton = () => {
239 | validate({
240 | name: { minlength: 3, maxlength: 7, required: true },
241 | email: { email: true },
242 | number: { numbers: true },
243 | date: { date: 'YYYY-MM-DD' },
244 | confirmPassword: { equalPassword: newPassword },
245 | });
246 | };
247 |
248 | return (
249 |
250 |
251 |
252 |
253 |
254 | {isFieldInError('date') &&
255 | getErrorsInField('date').map(errorMessage => (
256 | {errorMessage}
257 | ))}
258 |
259 |
264 |
269 | {isFieldInError('confirmPassword') &&
270 | getErrorsInField('confirmPassword').map(errorMessage => (
271 | {errorMessage}
272 | ))}
273 |
274 |
275 | Submit
276 |
277 |
278 | {getErrorMessages()}
279 |
280 | );
281 | };
282 |
283 | export default FormTest;
284 | ```
285 |
--------------------------------------------------------------------------------
/__tests__/functional-component.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { shallow, configure } from 'enzyme';
3 | import FunctionalFormTest from '../examples/FunctionalFormTest';
4 | import { TextInput, Text, TouchableHighlight } from 'react-native';
5 |
6 | import Adapter from 'enzyme-adapter-react-16';
7 | configure({ adapter: new Adapter() });
8 |
9 | describe('FunctionalComponent', () => {
10 | test('should display 7 text inputs', () => {
11 | const wrapper = shallow();
12 | const textInputs = wrapper.find(TextInput);
13 | expect(textInputs.length).toBe(7);
14 | });
15 |
16 | test('should display name error if name is lower than 3', () => {
17 | const wrapper = shallow();
18 | const textInput = wrapper.find(TextInput).at(0); // name input
19 | textInput.simulate('changeText', 'em');
20 |
21 | wrapper.find(TouchableHighlight).simulate('press');
22 | wrapper.update();
23 |
24 | const errorMsg = wrapper.find(Text).at(0);
25 | expect(errorMsg.contains(The field "name" length must be greater than 2.)).toBe(true);
26 | });
27 |
28 | test('should display name error if name is greater than 8', () => {
29 | const wrapper = shallow();
30 | const textInput = wrapper.find(TextInput).at(0); // name input
31 | textInput.simulate('changeText', 'mybeautifultest');
32 |
33 | wrapper.find(TouchableHighlight).simulate('press');
34 | wrapper.update();
35 |
36 | const errorMsg = wrapper.find(Text).at(0);
37 | expect(errorMsg.contains(The field "name" length must be lower than 7.)).toBe(true);
38 | });
39 |
40 | test('should display pseudo error if pseudo is not inquired', () => {
41 | const wrapper = shallow();
42 |
43 | wrapper.find(TouchableHighlight).simulate('press');
44 | wrapper.update();
45 |
46 | const errorMsg = wrapper.find(Text).at(1);
47 | expect(errorMsg.contains(The field "name" is mandatory.)).toBe(true);
48 | });
49 |
50 | test('should display email error if email is not a valid email', () => {
51 | const wrapper = shallow();
52 | const textInput = wrapper.find(TextInput).at(2); // email input
53 | textInput.simulate('changeText', 'wrongemai');
54 |
55 | wrapper.find(TouchableHighlight).simulate('press');
56 | wrapper.update();
57 |
58 | const errorMsg = wrapper.find(Text).at(3);
59 | expect(errorMsg.contains(The field "email" must be a valid email address.)).toBe(true);
60 | });
61 |
62 | test('should display email error if email is not a valid email', () => {
63 | const wrapper = shallow();
64 | const textInput = wrapper.find(TextInput).at(2); // email input
65 | textInput.simulate('changeText', 'wrongemai');
66 |
67 | wrapper.find(TouchableHighlight).simulate('press');
68 | wrapper.update();
69 |
70 | const errorMsg = wrapper.find(Text).at(3);
71 | expect(errorMsg.contains(The field "email" must be a valid email address.)).toBe(true);
72 | });
73 |
74 | test('should display number error if number is not a valid number', () => {
75 | const wrapper = shallow();
76 | const textInput = wrapper.find(TextInput).at(3); // number input
77 | textInput.simulate('changeText', 'abc');
78 |
79 | wrapper.find(TouchableHighlight).simulate('press');
80 | wrapper.update();
81 |
82 | const errorMsg = wrapper.find(Text).at(3);
83 | expect(errorMsg.contains(The field "number" must be a valid number.)).toBe(true);
84 | });
85 |
86 | test('should display date error if date is not a valid date pattern', () => {
87 | const wrapper = shallow();
88 | const textInput = wrapper.find(TextInput).at(4); // date input
89 | textInput.simulate('changeText', 'fdsfdf');
90 |
91 | wrapper.find(TouchableHighlight).simulate('press');
92 | wrapper.update();
93 |
94 | const errorMsg = wrapper.find(Text).at(3);
95 | expect(errorMsg.contains(The field "date" must be a valid date (YYYY-MM-DD).)).toBe(true);
96 | });
97 |
98 | test('should display confirm password error if passwords are not identical', () => {
99 | const wrapper = shallow();
100 | const password = wrapper.find(TextInput).at(5); // password input
101 | const confirmPassword = wrapper.find(TextInput).at(6); // confirm password input
102 | password.simulate('changeText', 'test');
103 | confirmPassword.simulate('changeText', 'test2');
104 |
105 | wrapper.find(TouchableHighlight).simulate('press');
106 | wrapper.update();
107 |
108 | const errorMsg = wrapper.find(Text);
109 | expect(errorMsg.contains(Passwords are different.)).toBe(true);
110 | });
111 | });
112 |
--------------------------------------------------------------------------------
/__tests__/validation-component.test.js:
--------------------------------------------------------------------------------
1 |
2 | import * as React from 'react';
3 | import { shallow, configure } from 'enzyme';
4 | import renderer from 'react-test-renderer';
5 | import FormTest from '../examples/FormTest';
6 | import { TextInput } from 'react-native';
7 |
8 | import Adapter from 'enzyme-adapter-react-16';
9 | configure({ adapter: new Adapter() });
10 |
11 | describe('ValidationComponent:', () => {
12 |
13 | test('initialize', () => {
14 | const component = renderer.create();
15 | expect(component.getInstance().errors).toEqual([]);
16 | });
17 |
18 | it('default fields validation should be ok', () => {
19 | const component = renderer.create();
20 | const formTest = component.getInstance();
21 |
22 | formTest._onPressButton();
23 |
24 | expect(formTest.errors).toEqual([]);
25 | expect(formTest.isFormValid()).toBe(true);
26 | expect(formTest.getErrorsInField('name')).toEqual([]);
27 | expect(formTest.getErrorsInField('number')).toEqual([]);
28 | expect(formTest.getErrorMessages()).toBe("");
29 | });
30 |
31 | it('method validate should return false', () => {
32 | const wrapper = shallow();
33 | const textInput = wrapper.find(TextInput).first(); // name input
34 | //Seize a value lower than 2 chars
35 | textInput.simulate('changeText', "na"); // minlength = 3
36 |
37 | const formTest = wrapper.instance();
38 | expect(formTest.validate({ name: { minlength: 3 } })).toBe(false);
39 | });
40 |
41 | it('empty field with required rule should not be ok', () => {
42 | const wrapper = shallow();
43 | const textInput = wrapper.find(TextInput).first(); // name input
44 | //Seize a value lower than 2 chars
45 | textInput.simulate('changeText', ""); // minlength = 3
46 |
47 | const formTest = wrapper.instance();
48 | expect(formTest.validate({ name: { required: true } })).toBe(false);
49 | });
50 |
51 | it('fields validation name should not be ok', () => {
52 | const wrapper = shallow();
53 | const textInput = wrapper.find(TextInput).first(); // name input
54 |
55 | //Seize a value lower than 2 chars
56 | textInput.simulate('changeText', "na"); // minlength = 3
57 |
58 | const formTest = wrapper.instance();
59 | formTest._onPressButton();
60 |
61 | expect(formTest.isFormValid()).toBe(false);
62 | expect(formTest.getErrorMessages()).toBe('The field "name" length must be greater than 2.');
63 | expect(formTest.isFieldInError('name')).toBe(true);
64 |
65 | // Seize an empty value
66 | textInput.simulate('changeText', ""); // minlength = 3
67 | formTest._onPressButton();
68 | expect(formTest.isFormValid()).toBe(false);
69 | expect(formTest.getErrorMessages()).toBe('The field "name" length must be greater than 2.\nThe field "name" is mandatory.');
70 | expect(formTest.getErrorsInField('name')).toEqual([
71 | 'The field "name" length must be greater than 2.',
72 | 'The field "name" is mandatory.'
73 | ])
74 | expect(formTest.isFieldInError('name')).toBe(true);
75 |
76 | // Seize a value greater than maxlength (7)
77 | textInput.simulate('changeText', "12345678"); // maxlength = 7
78 | formTest._onPressButton();
79 | expect(formTest.isFormValid()).toBe(false);
80 | expect(formTest.getErrorMessages()).toBe('The field "name" length must be lower than 7.');
81 | expect(formTest.isFieldInError('name')).toBe(true);
82 | });
83 |
84 | it('fields validation email should not be ok', () => {
85 | const wrapper = shallow();
86 | const textInput = wrapper.find(TextInput).at(1); // email input
87 | textInput.simulate('changeText', "em");
88 |
89 | const formTest = wrapper.instance();
90 | formTest._onPressButton();
91 |
92 | expect(formTest.isFormValid()).toBe(false);
93 | expect(formTest.getErrorMessages()).toBe('The field "email" must be a valid email address.');
94 | expect(formTest.isFieldInError('email')).toBe(true);
95 | });
96 |
97 |
98 |
99 | it('fields validation date should not be ok', () => {
100 | const wrapper = shallow();
101 | const textInput = wrapper.find(TextInput).at(3); // date input
102 | textInput.simulate('changeText', "fdsfds");
103 |
104 | const formTest = wrapper.instance();
105 | formTest._onPressButton();
106 |
107 | expect(formTest.isFormValid()).toBe(false);
108 | expect(formTest.getErrorMessages()).toBe('The field "date" must be a valid date (YYYY-MM-DD).');
109 | expect(formTest.isFieldInError('date')).toBe(true);
110 | });
111 |
112 |
113 | it('fields validation number should not be ok', () => {
114 | const wrapper = shallow();
115 | const textInput = wrapper.find(TextInput).at(2); // number input
116 | textInput.simulate('changeText', "not_number");
117 |
118 | const formTest = wrapper.instance();
119 | formTest._onPressButton();
120 |
121 | expect(formTest.isFormValid()).toBe(false);
122 | expect(formTest.getErrorMessages()).toBe('The field "number" must be a valid number.');
123 | expect(formTest.isFieldInError('number')).toBe(true);
124 | });
125 |
126 | it('fields validation number should not be very OK', () => {
127 | const wrapper = shallow();
128 | const textInput = wrapper.find(TextInput).at(2); // number input
129 | textInput.simulate('changeText', "not_number");
130 |
131 | const formTest = wrapper.instance();
132 | formTest._onPressButton();
133 |
134 | expect(formTest.isFormValid()).toBe(false);
135 | expect(formTest.getErrorMessages()).toBe('The field "number" must be a valid number.');
136 | expect(formTest.getErrorsInField('number')).toEqual(['The field "number" must be a valid number.'])
137 | expect(formTest.isFieldInError('number')).toBe(true);
138 | });
139 |
140 |
141 | it('deviceLocale props should be fr', () => {
142 | const wrapper = shallow();
143 | const formTest = wrapper.instance();
144 | expect(formTest.deviceLocale).toBe("fr");
145 | });
146 |
147 | it('error messages should be in fr locale', () => {
148 | const wrapper = shallow();
149 | const textInput = wrapper.find(TextInput).at(1); // email input
150 | textInput.simulate('changeText', "em");
151 |
152 | const formTest = wrapper.instance();
153 | formTest._onPressButton();
154 |
155 | expect(formTest.isFormValid()).toBe(false);
156 | expect(formTest.getErrorMessages()).toBe('Le champ "email" doit être une adresse email valide.');
157 | expect(formTest.isFieldInError('email')).toBe(true);
158 | expect(formTest.deviceLocale).toBe("fr");
159 | });
160 |
161 | it('rules props should be updated', () => {
162 | const rules = { any: /^(.*)$/ };
163 | const wrapper = shallow();
164 | const formTest = wrapper.instance();
165 | expect(formTest.rules).toEqual(rules);
166 | });
167 |
168 | it('messages props should be updated', () => {
169 | const messages = {
170 | en: { numbers: "error on numbers !" },
171 | fr: { numbers: "erreur sur les nombres !" }
172 | };
173 | const wrapper = shallow();
174 | const formTest = wrapper.instance();
175 | expect(formTest.messages).toEqual(messages);
176 | });
177 |
178 | it("fields password doesn't have number - not ok", () => {
179 | const wrapper = shallow();
180 | const textInput = wrapper.find(TextInput).at(4); // number input
181 | textInput.simulate('changeText', "Azerty*");
182 | const textInput2 = wrapper.find(TextInput).at(5); // number input
183 | textInput2.simulate('changeText', "Azerty*");
184 |
185 | const formTest = wrapper.instance();
186 | formTest._onPressButton();
187 |
188 | expect(formTest.isFormValid()).toBe(false);
189 | expect(formTest.getErrorMessages()).toBe('The field "password" must contain a number.');
190 | expect(formTest.isFieldInError('password')).toBe(true);
191 | });
192 |
193 | it("field password doesn't have special character - not ok", () => {
194 | const wrapper = shallow();
195 | const textInput = wrapper.find(TextInput).at(4); // number input
196 | textInput.simulate('changeText', "Azerty1");
197 | const textInput2 = wrapper.find(TextInput).at(5); // number input
198 | textInput2.simulate('changeText', "Azerty1");
199 |
200 | const formTest = wrapper.instance();
201 | formTest._onPressButton();
202 |
203 | expect(formTest.isFormValid()).toBe(false);
204 | expect(formTest.getErrorMessages()).toBe('The field "password" must contain a special character.');
205 | expect(formTest.isFieldInError('password')).toBe(true);
206 | });
207 |
208 | it("field password doesn't have lower case - not ok", () => {
209 | const wrapper = shallow();
210 | const textInput = wrapper.find(TextInput).at(4); // number input
211 | textInput.simulate('changeText', "A1*");
212 | const textInput2 = wrapper.find(TextInput).at(5); // number input
213 | textInput2.simulate('changeText', "A1*");
214 |
215 | const formTest = wrapper.instance();
216 | formTest._onPressButton();
217 |
218 | expect(formTest.isFormValid()).toBe(false);
219 | expect(formTest.getErrorMessages()).toBe('The field "password" must contain a lower case.');
220 | expect(formTest.isFieldInError('password')).toBe(true);
221 | });
222 |
223 | it("field password doesn't have upper case - not ok", () => {
224 | const wrapper = shallow();
225 | const textInput = wrapper.find(TextInput).at(4); // number input
226 | textInput.simulate('changeText', "a1*");
227 | const textInput2 = wrapper.find(TextInput).at(5); // number input
228 | textInput2.simulate('changeText', "a1*");
229 |
230 | const formTest = wrapper.instance();
231 | formTest._onPressButton();
232 |
233 | expect(formTest.isFormValid()).toBe(false);
234 | expect(formTest.getErrorMessages()).toBe('The field "password" must contain a upper case.');
235 | expect(formTest.isFieldInError('password')).toBe(true);
236 | });
237 |
238 | it("field password is not equal to confirm password - not ok", () => {
239 | const wrapper = shallow();
240 | const textInput = wrapper.find(TextInput).at(4); // number input
241 | textInput.simulate('changeText', "Aa1*");
242 | const textInput2 = wrapper.find(TextInput).at(5); // number input
243 | textInput2.simulate('changeText', "a1*");
244 |
245 | const formTest = wrapper.instance();
246 | formTest._onPressButton();
247 |
248 | expect(formTest.isFormValid()).toBe(false);
249 | expect(formTest.getErrorMessages()).toBe('Passwords are different.');
250 | expect(formTest.isFieldInError('confirmPassword')).toBe(true);
251 | });
252 |
253 | it("password fields are ok", () => {
254 | const wrapper = shallow();
255 | const textInput = wrapper.find(TextInput).at(4); // number input
256 | textInput.simulate('changeText', "Aa1*");
257 | const textInput2 = wrapper.find(TextInput).at(5); // number input
258 | textInput2.simulate('changeText', "Aa1*");
259 |
260 | const formTest = wrapper.instance();
261 | formTest._onPressButton();
262 |
263 | expect(formTest.isFormValid()).toBe(true);
264 | expect(formTest.getErrorMessages()).toBe("");
265 | expect(formTest.isFieldInError('password')).toBe(false);
266 | expect(formTest.isFieldInError('confirmPassword')).toBe(false);
267 | });
268 |
269 | });
270 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:metro-react-native-babel-preset'],
3 | };
--------------------------------------------------------------------------------
/examples/FormTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Text, TextInput, TouchableHighlight, View } from 'react-native';
3 | import ValidationComponent from '../src/index';
4 |
5 | export default class FormTest extends ValidationComponent {
6 |
7 | constructor(props) {
8 | super(props);
9 | this.state = { name: "My name", email: "tibtib@gmail.com", number: "56", date: "2017-03-01", password: "", confirmPassword: "", error: false };
10 | }
11 |
12 | _onPressButton() {
13 | // Call ValidationComponent validate method
14 | this.validate({
15 | name: { minlength: 3, maxlength: 7, required: true },
16 | email: { email: true },
17 | number: { numbers: true },
18 | date: { date: 'YYYY-MM-DD' },
19 | password: { hasNumber: true, hasLowerCase: true, hasUpperCase: true, hasSpecialCharacter: true },
20 | confirmPassword: { equalPassword: this.state.password },
21 | });
22 | }
23 |
24 | render() {
25 | return (
26 |
27 | this.setState({ name })} value={this.state.name} />
28 | this.setState({ email })} value={this.state.email} />
29 | this.setState({ number })} value={this.state.number} />
30 | this.setState({ date })} value={this.state.date} />
31 | this.setState({ password })} value={this.state.password} />
32 | this.setState({ confirmPassword })} value={this.state.confirmPassword} />
33 |
34 |
35 | Submit
36 |
37 |
38 |
39 | {this.getErrorMessages()}
40 |
41 |
42 | );
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/examples/FunctionalFormTest.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { View, Text, TextInput, TouchableHighlight } from 'react-native';
3 | import { useValidation } from '../src/index';
4 |
5 | const FunctionalFormTest = () => {
6 | const [name, setName] = useState('');
7 | const [pseudo, setPseudo] = useState('');
8 | const [email, setEmail] = useState('');
9 | const [number, setNumber] = useState('');
10 | const [date, setDate] = useState('');
11 | const [newPassword, setNewPassword] = useState('');
12 | const [confirmPassword, setConfirmPassword] = useState('');
13 |
14 | const { validate, isFieldInError, getErrorsInField, getErrorMessages } = useValidation({
15 | state: { name, email, pseudo, number, date, newPassword, confirmPassword }
16 | });
17 |
18 | const _onPressButton = () => {
19 | validate({
20 | name: { minlength: 3, maxlength: 7, required: true },
21 | pseudo: { required: true },
22 | email: { email: true },
23 | number: { numbers: true },
24 | date: { date: 'YYYY-MM-DD' },
25 | confirmPassword: { equalPassword: newPassword }
26 | });
27 | };
28 |
29 | return (
30 |
31 |
32 | {isFieldInError('name') &&
33 | getErrorsInField('name').map((errorMessage) => {errorMessage})}
34 |
35 |
36 | {isFieldInError('pseudo') &&
37 | getErrorsInField('pseudo').map((errorMessage) => {errorMessage})}
38 |
39 |
40 | {isFieldInError('email') &&
41 | getErrorsInField('email').map((errorMessage) => {errorMessage})}
42 |
43 |
44 | {isFieldInError('number') && getErrorsInField('number').map((errorMessage) => {errorMessage})}
45 |
46 |
47 | {isFieldInError('date') && getErrorsInField('date').map((errorMessage) => {errorMessage})}
48 |
49 |
50 |
51 | {isFieldInError('confirmPassword') &&
52 | getErrorsInField('confirmPassword').map((errorMessage) => {errorMessage})}
53 |
54 |
55 | Submit
56 |
57 |
58 | {getErrorMessages()}
59 |
60 | );
61 | };
62 |
63 | export default FunctionalFormTest;
64 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-form-validator",
3 | "version": "0.5.0",
4 | "description": "React native library to validate form fields",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "start": "node node_modules/react-native/local-cli/cli.js start",
8 | "test": "jest",
9 | "test:watch": "jest --watch",
10 | "commit": "git-cz"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/perscrew/react-native-form-validator.git"
15 | },
16 | "keywords": [
17 | "react-native",
18 | "react",
19 | "javascript"
20 | ],
21 | "author": "Thibaud Dervily",
22 | "license": "ISC",
23 | "bugs": {
24 | "url": "https://github.com/perscrew/react-native-form-validator/issues"
25 | },
26 | "homepage": "https://github.com/perscrew/react-native-form-validator#readme",
27 | "dependencies": {
28 | "moment": "^2.11.2",
29 | "prop-types": "15.6.0"
30 | },
31 | "devDependencies": {
32 | "@babel/core": "^7.15.0",
33 | "@babel/plugin-syntax-optional-chaining": "^7.8.3",
34 | "@babel/preset-env": "^7.15.0",
35 | "@babel/preset-react": "^7.14.5",
36 | "@commitlint/cli": "^13.1.0",
37 | "@commitlint/config-conventional": "^13.1.0",
38 | "@types/jest": "^27.0.1",
39 | "babel-jest": "^27.0.6",
40 | "commitizen": "^4.2.4",
41 | "cz-conventional-changelog": "^1.1.6",
42 | "enzyme": "^3.11.0",
43 | "enzyme-adapter-react-16": "^1.15.6",
44 | "jest": "^27.0.6",
45 | "jsdom": "^9.4.1",
46 | "metro-react-native-babel-preset": "^0.66.2",
47 | "react": "^16.13.1",
48 | "react-dom": "^16.13.1",
49 | "react-native": "^0.65.1",
50 | "react-test-renderer": "^17.0.2"
51 | },
52 | "jest": {
53 | "preset": "react-native"
54 | },
55 | "commitlint": {
56 | "extends": [
57 | "@commitlint/config-conventional"
58 | ]
59 | },
60 | "config": {
61 | "commitizen": {
62 | "path": "./node_modules/cz-conventional-changelog"
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/ValidationComponent.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { Component } from 'react';
3 | import defaultMessages from './messages/defaultMessages';
4 | import defaultRules from './rules/defaultRules';
5 |
6 | export default class ValidationComponent extends Component {
7 | constructor(props) {
8 | super(props);
9 | // array to store error on each fields
10 | // ex:
11 | // [{ fieldName: "name", messages: ["The field name is required."] }]
12 | this.errors = [];
13 | // Retrieve props
14 | this.deviceLocale = props.deviceLocale || 'en'; // ex: en, fr
15 | this.rules = props.rules || defaultRules; // rules for Validation
16 | this.messages = props.messages || defaultMessages;
17 | this.labels = props.labels || {};
18 | this.state = { error: false };
19 | }
20 |
21 | /*
22 | * Method validate to verify if each children respect the validator rules
23 | * Fields example (Array) :
24 | * fields = {
25 | * input1: {
26 | * required:true,
27 | * numbers:true,
28 | * maxLength:5
29 | * }
30 | *}
31 | */
32 | validate(fields) {
33 | // Reset errors
34 | this._resetErrors();
35 | // Iterate over inner state
36 | for (const key of Object.keys(this.state)) {
37 | // Check if child name is equals to fields array set up in parameters
38 | const rules = fields[key];
39 | if (rules) {
40 | // Check rule for current field
41 | this._checkRules(key, rules, this.state[key]);
42 | }
43 | }
44 | return this.isFormValid();
45 | }
46 |
47 | // Method to check rules on a spefific field
48 | _checkRules(fieldName, rules, value) {
49 | if (!value && !rules.required) {
50 | return; // if value is empty AND its not required by the rules, no need to check any other rules
51 | }
52 | for (const key of Object.keys(rules)) {
53 | const isRuleFn = typeof this.rules[key] == 'function';
54 | const isRegExp = this.rules[key] instanceof RegExp;
55 | if ((isRuleFn && !this.rules[key](rules[key], value)) || (isRegExp && !this.rules[key].test(value))) {
56 | this._addError(fieldName, key, rules[key], isRuleFn);
57 | }
58 | }
59 | }
60 |
61 | // Add error
62 | // ex:
63 | // [{ fieldName: "name", messages: ["The field name is required."] }]
64 | _addError(fieldName, rule, value, isFn) {
65 | const name = this.labels[fieldName];
66 | value = rule == 'minlength' ? value - 1 : value;
67 | const errMsg = this.messages[this.deviceLocale][rule].replace('{0}', name || fieldName).replace('{1}', value);
68 | let [error] = this.errors.filter((err) => err.fieldName === fieldName);
69 | // error already exists
70 | if (error) {
71 | // Update existing element
72 | const index = this.errors.indexOf(error);
73 | error.messages.push(errMsg);
74 | error.failedRules.push(rule);
75 | this.errors[index] = error;
76 | } else {
77 | // Add new item
78 | this.errors.push({
79 | fieldName,
80 | failedRules: [rule],
81 | messages: [errMsg]
82 | });
83 | }
84 | // this.setState({ error: true });
85 | }
86 |
87 | // Reset error fields
88 | _resetErrors() {
89 | this.errors = [];
90 | this.setState({ error: false });
91 | }
92 |
93 | // Method to check if the field is in error
94 | isFieldInError(fieldName) {
95 | return this.errors.filter((err) => err.fieldName === fieldName).length > 0;
96 | }
97 |
98 | isFormValid() {
99 | return this.errors.length == 0;
100 | }
101 |
102 | // Return an object where the keys are the field names and the value is an array with the rules that failed validation
103 | getFailedRules() {
104 | let failedRulesPerField = {};
105 | for (let index = 0; index < this.errors.length; index++) {
106 | let error = this.errors[index];
107 | failedRulesPerField[error.fieldName] = error.failedRules;
108 | }
109 | return failedRulesPerField;
110 | }
111 |
112 | // Return the rules that failed validation for the given field
113 | getFailedRulesInField(fieldName) {
114 | const foundError = this.errors.find((err) => err.fieldName === fieldName);
115 | if (!foundError) {
116 | return [];
117 | }
118 | return foundError.failedRules;
119 | }
120 |
121 | // Concatenate each error messages
122 | getErrorMessages(separator = '\n') {
123 | return this.errors.map((err) => err.messages.join(separator)).join(separator);
124 | }
125 |
126 | // Method to return errors on a specific field
127 | getErrorsInField(fieldName) {
128 | const foundError = this.errors.find((err) => err.fieldName === fieldName);
129 | if (!foundError) {
130 | return [];
131 | }
132 | return foundError.messages;
133 | }
134 | }
135 |
136 | // PropTypes for component
137 | ValidationComponent.propTypes = {
138 | deviceLocale: PropTypes.string, // Used for language locale
139 | rules: PropTypes.object, // rules for validations
140 | messages: PropTypes.object, // messages for validation errors
141 | labels: PropTypes.object // labels for validation messages
142 | };
143 | // DefaultProps for component
144 | ValidationComponent.defaultProps = {
145 | deviceLocale: 'en',
146 | rules: defaultRules,
147 | messages: defaultMessages,
148 | labels: {}
149 | };
150 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import useValidation from "./use-validation";
2 | import ValidationComponent from "./ValidationComponent";
3 |
4 | export { useValidation };
5 | export default ValidationComponent;
6 |
--------------------------------------------------------------------------------
/src/messages/defaultMessages.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const defaultMessages = {
4 | // English language - Used by default
5 | en: {
6 | numbers: 'The field "{0}" must be a valid number.',
7 | email: 'The field "{0}" must be a valid email address.',
8 | required: 'The field "{0}" is mandatory.',
9 | date: 'The field "{0}" must be a valid date ({1}).',
10 | minlength: 'The field "{0}" length must be greater than {1}.',
11 | maxlength: 'The field "{0}" length must be lower than {1}.',
12 | equalPassword: 'Passwords are different.',
13 | hasUpperCase: 'The field "{0}" must contain a upper case.',
14 | hasLowerCase: 'The field "{0}" must contain a lower case.',
15 | hasNumber: 'The field "{0}" must contain a number.',
16 | hasSpecialCharacter: 'The field "{0}" must contain a special character.',
17 | },
18 | //Arabic language
19 | ar: {
20 | numbers: 'الحقل "{0}" يجب أن يكون رقم صحيح',
21 | email: 'الحقل "{0}" يجب أن يكون بريد إلكتروني صحيح',
22 | required: 'الحقل "{0}" مطلوب',
23 | date: 'الحقل "{0}" يجب أن يكون تاريخ صحيح ({1}).',
24 | minlength: 'الحقل "{0}" يجب أن يكون أكثر من {1}.',
25 | maxlength: 'الحقل "{0}" يجب أن يكون أقل من {1}.',
26 | equalPassword: 'كلمات المرور مختلفة.',
27 | hasNumber: 'يجب أن يحتوي الحقل "{0}" على رقم.',
28 | hasUpperCase: 'يجب أن يحتوي الحقل "{0}" على حرف كبير',
29 | hasLowerCase: 'يجب أن يحتوي الحقل "{0}" على أحرف صغيرة',
30 | hasSpecialCharacter: 'يجب أن يحتوي الحقل "{0}" على رمز خاص',
31 | },
32 | // French language
33 | fr: {
34 | numbers: 'Le champ "{0}" doit être un nombre valide.',
35 | email: 'Le champ "{0}" doit être une adresse email valide.',
36 | required: 'Le champ "{0}" est obligatoire.',
37 | date: 'Le champ "{0}" doit correspondre à une date valide ({1}).',
38 | minlength: 'Le nombre de caractère du champ "{0}" doit être supérieur à {1}.',
39 | maxlength: 'Le nombre de caractère du champ "{0}" doit être inférieur à {1}.',
40 | equalPassword: 'Les mots de passes sont differents',
41 | hasNumber: 'Le champ "{0}" doit contenir un chiffre.',
42 | hasUpperCase: 'Le champ "{0}" doit contenir une majuscule',
43 | hasLowerCase: 'Le champ "{0}" doit contenir une minuscule',
44 | hasSpecialCharacter: 'Le champ "{0}" doit contenir un caractère spécial',
45 | },
46 | // Persian (Farsi) language
47 | fa: {
48 | numbers: 'فیلد "{0}" باید یک عدد باشد.',
49 | email: 'فیلد "{0}" باید یک آدرس ایمیل باشد.',
50 | required: 'فیلد "{0}" نباید خالی باشد.',
51 | date: 'فیلد "{0}" باید یک تاریخ ({1}) باشد.',
52 | minlength: 'طول فیلد "{0}" باید بیشتر از "{1}" باشد.',
53 | maxlength: 'طول فیلد "{0}" باید کمتر از "{1}" باشد.',
54 | equalPassword: 'رمزهای عبور متفاوت هستند',
55 | hasNumber: 'قسمت "{0}" باید حاوی یک عدد باشد.',
56 | hasUpperCase: 'قسمت "{0}" باید شامل یک حرف بزرگ باشد',
57 | hasLowerCase: 'قسمت "{0}" باید دارای حروف کوچک باشد',
58 | hasSpecialCharacter: 'قسمت "{0}" باید دارای یک نویسه خاص باشد',
59 | },
60 | // Indonesian language
61 | id: {
62 | numbers: '"{0}" harus berupa angka',
63 | email: 'Format email pada "{0}" harus valid',
64 | required: '"{0}" harus diisi',
65 | date: 'Format tanggal pada "{0}" harus ({1})',
66 | minlength: '"{0}" harus lebih dari {1} karakter',
67 | maxlength: '"{0}" harus kurang dari {1} karakter',
68 | equalPassword: 'Kata sandi berbeda',
69 | hasNumber: '"{0}" harus berisi angka.',
70 | hasUpperCase: '"{0}" harus mengandung huruf kapital',
71 | hasLowerCase: '"{0}" harus berisi huruf kecil',
72 | hasSpecialCharacter: '"{0}" harus berisi karakter khusus',
73 | },
74 | // Portuguese language of Brazil
75 | ptBR: {
76 | numbers: 'O campo "{0}" precisar conter um número válido.',
77 | email: 'O campo "{0}" precisa conter um email válido.',
78 | required: 'O campo "{0}" é obrigatório.',
79 | date: 'O campo "{0}" precisa conter uma data válida ({1}).',
80 | minlength: 'O campo "{0}" precisa ser maior que {1} caracteres.',
81 | maxlength: 'O campo "{0}" precisa ser menor que {1} caracteres.',
82 | equalPassword: 'As senhas são diferentes',
83 | hasNumber: 'O campo "{0}" deve conter um número.',
84 | hasUpperCase: 'O campo "{0}" deve conter uma letra maiúscula',
85 | hasLowerCase: 'O campo "{0}" deve conter letras minúsculas',
86 | hasSpecialCharacter: 'O campo "{0}" deve conter um caractere especial',
87 | },
88 | // Spanish language
89 | es: {
90 | numbers: 'El campo "{0}" debe ser un número válido.',
91 | email: 'El campo "{0}" debe ser un email válido.',
92 | required: 'El campo "{0}" es requerido.',
93 | date: 'El campo "{0}" debe contener una fecha válida ({1}).',
94 | minlength: 'La longitud del campo "{0}" debe ser mayor que {1} caracteres',
95 | maxlength: 'La longitud del campo "{0}" debe ser menor que {1} caracteres.',
96 | equalPassword: 'Las contraseñas son diferentes',
97 | hasNumber: 'El campo "{0}" debe contener un número.',
98 | hasUpperCase: 'El campo "{0}" debe contener una letra mayúscula',
99 | hasLowerCase: 'El campo "{0}" debe contener minúsculas',
100 | hasSpecialCharacter: 'El campo "{0}" debe contener un carácter especial',
101 | },
102 | // Turkish language - Used by default
103 | tr: {
104 | numbers: '"{0}" alanı geçerli bir sayı olmalıdır.',
105 | email: '"{0}" alanı geçerli bir email adresi olmalıdır.',
106 | required: '"{0}" alanı gereklidir.',
107 | date: '"{0}" alanı geçerli bir tarih olmalıdır. ({1}).',
108 | minlength: '"{0}" alanı {1} karakterden daha uzun olmalıdır.',
109 | maxlength: '"{0}" alanı {1} karakterden daha kısa olmalıdır.',
110 | equalPassword: 'Şifreler farklı',
111 | hasNumber: '"{0}" alanı bir sayı içermelidir.',
112 | hasUpperCase: '"{0}" alanı büyük harf içermelidir',
113 | hasLowerCase: '"{0}" alanı küçük harf içermelidir',
114 | hasSpecialCharacter: '"{0}" alanı özel bir karakter içermelidir',
115 | },
116 | it: {
117 | numbers: 'Il campo "{0}" deve contenere solo numeri.',
118 | email: 'Il campo "{0}" deve contenere un indirizzo email valido.',
119 | required: 'Il campo "{0}" é obbligatorio.',
120 | date: 'Il campo "{0}" deve contenere una data valida ({1}).',
121 | minlength: 'Il campo "{0}" deve contenere almeno {1} caratteri.',
122 | maxlength: 'Il campo "{0}" deve contenere meno di {1} caratteri.',
123 | equalPassword: 'Le passwords inserite sono differenti',
124 | hasUpperCase: 'Il campo "{0}" deve contenere una maiuscola',
125 | hasLowerCase: 'Il campo "{0}" deve contenere una minuscola',
126 | hasNumber: 'Il campo "{0}" deve contenere un numero',
127 | hasSpecialCharacter: 'Il campo "{0}" deve contenere un carattere speciale',
128 | },
129 | // TODO Add other languages here...
130 | };
131 |
132 | export default defaultMessages;
133 |
--------------------------------------------------------------------------------
/src/rules/defaultRules.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import moment from 'moment';
4 |
5 | // Custom default rules to validate form fields
6 | const defaultRules = {
7 | numbers: /^(([0-9]*)|(([0-9]*)\.([0-9]*)))$/,
8 | email:
9 | /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
10 | required: /\S+/,
11 | hasNumber: /\d/,
12 | hasUpperCase: /(?=.*[A-Z])/,
13 | hasLowerCase: /(?=.*[a-z])/,
14 | hasSpecialCharacter: /(\W)/,
15 | date(format = 'YYYY-MM-DD', value) {
16 | const d = moment(value, format);
17 | if (d == null || !d.isValid()) return false;
18 | return true;
19 | },
20 | minlength(length, value) {
21 | if (length === void 0) {
22 | throw 'ERROR: It is not a valid length, checkout your minlength settings.';
23 | } else if (value.length >= length) {
24 | return true;
25 | }
26 | return false;
27 | },
28 | maxlength(length, value) {
29 | if (length === void 0) {
30 | throw 'ERROR: It is not a valid length, checkout your maxlength settings.';
31 | } else if (value.length > length) {
32 | return false;
33 | }
34 | return true;
35 | },
36 | equalPassword(dataToCompare, value) {
37 | return dataToCompare === value;
38 | }
39 | };
40 |
41 | export default defaultRules;
42 |
--------------------------------------------------------------------------------
/src/use-validation.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useState, useRef } from 'react';
3 | import defaultMessages from './messages/defaultMessages';
4 | import defaultRules from './rules/defaultRules';
5 |
6 | const useValidation = (props) => {
7 | const { state = {} } = props;
8 | // array to store error on each fields
9 | // ex:
10 | // [{ fieldName: "name", messages: ["The field name is required."] }]
11 | // Retrieve props
12 | const deviceLocale = props.deviceLocale || 'en'; // ex: en, fr
13 | const baseRules = props.rules || defaultRules; // rules for Validation
14 | const messages = props.messages || defaultMessages;
15 | const labels = props.labels || {};
16 | const errors = useRef([]);
17 | const [internalErrors, setInternalErrors] = useState([]);
18 |
19 | /*
20 | * Method validate to verify if each children respect the validator rules
21 | * Fields example (Array) :
22 | * fields = {
23 | * input1: {
24 | * required:true,
25 | * numbers:true,
26 | * maxLength:5
27 | * }
28 | *}
29 | */
30 | const validate = (fields) => {
31 | // Reset errors
32 | _resetErrors();
33 | // Iterate over inner state
34 | for (const key of Object.keys(state)) {
35 | // Check if child name is equals to fields array set up in parameters
36 | const rules = fields[key];
37 | if (rules) {
38 | // Check rule for current field
39 | _checkRules(key, rules, state[key]);
40 | }
41 | }
42 | return isFormValid();
43 | };
44 |
45 | // Method to check rules on a spefific field
46 | const _checkRules = (fieldName, rules, value) => {
47 | if (!value && !rules.required) {
48 | return; // if value is empty AND its not required by the rules, no need to check any other rules
49 | }
50 | for (const key of Object.keys(rules)) {
51 | const isRuleFn = typeof baseRules[key] == 'function';
52 | const isRegExp = baseRules[key] instanceof RegExp;
53 | if ((isRuleFn && !baseRules[key](rules[key], value)) || (isRegExp && !baseRules[key].test(value))) {
54 | _addError(fieldName, key, rules[key], isRuleFn);
55 | }
56 | }
57 | };
58 |
59 | // Add error
60 | // ex:
61 | // [{ fieldName: "name", messages: ["The field name is required."] }]
62 | const _addError = (fieldName, rule, value, isFn) => {
63 | const name = labels[fieldName];
64 | value = rule == 'minlength' ? value - 1 : value;
65 | const errMsg = messages[deviceLocale][rule].replace('{0}', name || fieldName).replace('{1}', value);
66 | let [error] = errors.current.filter((err) => err.fieldName === fieldName);
67 | // error already exists
68 | if (error) {
69 | // Update existing element
70 | const index = errors.current.indexOf(error);
71 | error.messages.push(errMsg);
72 | error.failedRules.push(rule);
73 | errors.current[index] = error;
74 | } else {
75 | // Add new item
76 | errors.current.push({
77 | fieldName,
78 | failedRules: [rule],
79 | messages: [errMsg]
80 | });
81 | setInternalErrors(errors.current);
82 | }
83 | };
84 |
85 | // Reset error fields
86 | const _resetErrors = () => {
87 | errors.current = [];
88 | setInternalErrors([]);
89 | };
90 |
91 | // Method to check if the field is in error
92 | const isFieldInError = (fieldName) => {
93 | return internalErrors.filter((err) => err.fieldName === fieldName).length > 0;
94 | };
95 |
96 | const isFormValid = () => {
97 | return errors.current?.length == 0;
98 | };
99 |
100 | // Return an object where the keys are the field names and the value is an array with the rules that failed validation
101 | const getFailedRules = () => {
102 | let failedRulesPerField = {};
103 | for (let index = 0; index < internalErrors.length; index++) {
104 | let error = internalErrors[index];
105 | failedRulesPerField[error.fieldName] = error.failedRules;
106 | }
107 | return failedRulesPerField;
108 | };
109 |
110 | // Return the rules that failed validation for the given field
111 | const getFailedRulesInField = (fieldName) => {
112 | const foundError = internalErrors.find((err) => err.fieldName === fieldName);
113 | if (!foundError) {
114 | return [];
115 | }
116 | return foundError.failedRules;
117 | };
118 |
119 | // Concatenate each error messages
120 | const getErrorMessages = (separator = '\n') => {
121 | return internalErrors.map((err) => err.messages.join(separator)).join(separator);
122 | };
123 |
124 | // Method to return errors on a specific field
125 | const getErrorsInField = (fieldName) => {
126 | const foundError = internalErrors.find((err) => err.fieldName === fieldName);
127 | if (!foundError) {
128 | return [];
129 | }
130 | return foundError.messages;
131 | };
132 |
133 | return {
134 | validate,
135 | isFormValid,
136 | isFieldInError,
137 | getFailedRules,
138 | getFailedRulesInField,
139 | getErrorMessages,
140 | getErrorsInField
141 | };
142 | };
143 |
144 | useValidation.propTypes = {
145 | deviceLocale: PropTypes.string, // Used for language locale
146 | rules: PropTypes.object, // rules for validations
147 | messages: PropTypes.object, // messages for validation errors
148 | labels: PropTypes.object // labels for validation messages
149 | };
150 |
151 | useValidation.defaultProps = {
152 | deviceLocale: 'en',
153 | rules: defaultRules,
154 | messages: defaultMessages,
155 | labels: {}
156 | };
157 |
158 | export default useValidation;
159 |
--------------------------------------------------------------------------------