├── .babelrc ├── .gitignore ├── README.md ├── dist └── index.js ├── example ├── .gitignore ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── App.js │ ├── App.test.js │ ├── index.js │ └── registerServiceWorker.js └── yarn.lock ├── package-lock.json ├── package.json ├── src ├── Form.js ├── Form.test.js ├── Input.js ├── index.js └── utils │ ├── .DS_Store │ ├── Error.js │ ├── Validate.js │ ├── factory.js │ ├── index.js │ ├── integration │ └── form.test.js │ └── unit │ └── validation.test.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | "react" 6 | ] 7 | ], 8 | "plugins": ["transform-class-properties", "transform-object-rest-spread"], 9 | "env": { 10 | "test": { 11 | "presets": [ 12 | "env", 13 | "react" 14 | ], 15 | "plugins":[ 16 | "transform-class-properties", 17 | "transform-object-rest-spread" 18 | ], 19 | "only": [ 20 | "./**/*.js", 21 | "node_modules/jest-runtime" 22 | ] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Form Validation 2 | 3 | ## Table of content 4 | 5 | 6 | 7 | 8 | **Table of Contents** _generated with [DocToc](https://github.com/thlorenz/doctoc)_ 9 | 10 | - [React Form Validation](#react-form-validation) 11 | - [Table of content](#table-of-content) 12 | - [Introduction](#introduction) 13 | - [Installation](#installation) 14 | - [Example](#example) 15 | - [Available Props](#available-props) 16 | - [Children Function](#children-function) 17 | - [Arguments in the children function](#arguments-in-the-children-function) 18 | - [data](#data) 19 | - [isValidate](#isvalidate) 20 | - [errors](#errors) 21 | - [onChange](#onchange) 22 | - [onSubmit](#onsubmit) 23 | - [Rules](#rules) - [Relation between Rule and Data](#relation-between-rule-and-data) - [Using Parameters](#using-parameters) - [Rules As Array](#rules-as-array) - [Rules As String](#rules-as-string) - [Rules As Object and Custom error message](#rules-as-object-and-custom-error-message) - [Custom message For every fail rule](#custom-message-for-every-fail-rule) - [Custom message For detail fail rule](#custom-message-for-detail-fail-rule) - [Available Rules](#available-rules) 24 | - [To do](#to-do) 25 | - [Thanks to](#thanks-to) 26 | 27 | 28 | 29 | ## Introduction 30 | 31 | Wrapper component with ability to validating any user input with various available rules using render props pattern. 32 | Inspired by laravel validation rules 33 | 34 | # Installation 35 | 36 | Using Npm 37 | 38 | ``` 39 | npm install react-form-validation-render-prop 40 | ``` 41 | 42 | Using Yarn 43 | 44 | ``` 45 | yarn add react-form-validation-render-props 46 | ``` 47 | 48 | ## Example 49 | 50 | ```javascript 51 | import React, { Component } from "react"; 52 | import { Form } from "react-form-validation-render-props"; 53 | 54 | class App extends Component { 55 | state = { 56 | email: "", 57 | password: "" 58 | }; 59 | 60 | // rules using array 61 | // rules = { 62 | // email: ["required", "email"], 63 | // password: ["required", "between:5:10"] 64 | // }; 65 | 66 | // rules using string 67 | // rules = { 68 | // email: 'required|email', 69 | // password: 'required|between:5:10' 70 | // } 71 | 72 | // rules using object and custom global message and custom message 73 | rules = { 74 | email: { 75 | rules: ["required", "email"], 76 | message: "Please allow me to fill your inbox" 77 | }, 78 | password: { 79 | rules: "required|between:5:10", 80 | message: { 81 | required: "Allow yourself to come to our system", 82 | between: "Make yourself secure" 83 | } 84 | } 85 | }; 86 | 87 | onChangeValue = (key, value) => { 88 | this.setState({ [key]: value }); 89 | }; 90 | 91 | onSubmit = (e, valid) => { 92 | e.preventDefault(); 93 | console.log(valid); 94 | }; 95 | 96 | render() { 97 | return ( 98 |
104 | {({ isValidate, errors, onChange, data, onSubmit }) => { 105 | return ( 106 | 107 | {data.map(item => { 108 | return ( 109 |
110 | 111 | 112 | {!isValidate && ( 113 | 114 | {errors.get(item.key)} 115 | 116 | )} 117 |
118 | ); 119 | })} 120 | 121 |
122 | ); 123 | }} 124 | 125 | ); 126 | } 127 | } 128 | 129 | export default App; 130 | ``` 131 | 132 | ## Available Props 133 | 134 | | Name | Type | Description | Example | Parameters | 135 | | ---------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | 136 | | data | `Object` | Data that will be validated | `data={email: 'semmivp1@gmail.com, fullName: 'semmi verian}` | 137 | | rules | `Object/ String / Array` | Collection of rules that will run the validation | [More Detail](#rules) | 138 | | onChangeValue | `function` | Function that will be called whenever internal `onChange` function is triggered | the `key` and the `value` of what data is being updated. | 1. `key` => the key of data which is being updated
2. `value` => the updated value | 139 | | onSubmit | `function` | Function that will be called whenever interval `onSubmit` function is triggered | `onSubmit=function onSubmit (e, valid) { // code here }` | 1. `e` => event that being triggered
2. `valid` => flag that will tell whether the validation is passed or failed | 140 | | children | `function` | Children function that will be rendered whatever view you want with utilities given from the library | `children=({ isValidate, errors, onChange, data, onSubmit }) => {})`
Link ke detail | [More Detail](#children-function) | 141 | | validateOnChange | `boolean` | Flag that tell library to validate user input whenever internal `onChange` function is triggered default to `true` | validateOnChange={true} | 142 | 143 | ## Children Function 144 | 145 | By Using Children function you have 100% authority to use whatever style you want that will be apply to this library. 146 | 147 | There are two options for you to define the Children props. You can choose what ever style you prefer to define the children function. 148 | 149 | The First Option 150 | 151 | ```Javascript 152 |
{ 154 | return (
) 155 | }}> 156 | ``` 157 | 158 | The Second Option 159 | 160 | ```javascript 161 |
162 | {({ isValidate, errors, onChange, data, onSubmit }) => { 163 | return ; 164 | }} 165 |
166 | ``` 167 | 168 | #### Arguments in the children function 169 | 170 | In this section we will cover each argument that available inside our children function 171 | 172 | Example 173 | 174 | ```javascript 175 |
176 | {({ isValidate, errors, onChange, data, onSubmit }) => { 177 | return ; 178 | }} 179 |
180 | ``` 181 | 182 | ##### data 183 | 184 | is an `Array` that you can use to reference what data is available for you to render. 185 | 186 | data structure 187 | 188 | ```javascript 189 | [ 190 | { key: "email", value: "semmivp1@gmail.com" }, 191 | { key: "fullName", value: "semmi verian putera" } 192 | ]; 193 | ``` 194 | 195 | example 196 | 197 | ```javascript 198 | { 199 | ({ isValidate, errors, onChange, data, onSubmit }) => { 200 | return ( 201 |
202 | {data.map(item => { 203 | return ( 204 |
205 | 206 | 207 | {!isValidate && ( 208 | {errors.get(item.key)} 209 | )} 210 |
211 | ); 212 | })} 213 | 214 |
215 | ); 216 | }; 217 | } 218 | ``` 219 | 220 | In above example we use the `data` arguments to map all available data from our inner component to render at consumer component. 221 | 222 | ##### isValidate 223 | 224 | is a `boolean` to tell you whether all the validation rules is success or failed 225 | 226 | ##### errors 227 | 228 | is a `Class` that will tell you what are the errors for the failed validation 229 | 230 | to get the errors for the given key you can use our function like this 231 | 232 | ```javascript 233 | {errors.get(item.key)} 234 | ``` 235 | 236 | it will return whatever error message that exist at the given key. 237 | 238 | ##### onChange 239 | 240 | is a `function` that you should apply to let our internal component know what changes from the consumer component and will trigger the validation rules if you set the `validateOnChange` flag to `true`. Whenever this function is called our internal component will trigger a props function `onChangeValue` so you can update your state whenever the input is changing. This function need one arguments which is the key will be updated by the new value. In the above example if we define `"email"` as the argument then our component will update the `"email"` value at our internal component state. 241 | 242 | example 243 | 244 | ```javascript 245 | { 257 | return
; 258 | }; 259 | } 260 | ``` 261 | 262 | ## Rules 263 | 264 | ##### Relation between Rule and Data 265 | 266 | every rules need an object with `[key]: [value]` pair like this example. 267 | 268 | ```javascript 269 | rules = { 270 | email: ["required", "email"], 271 | password: ["required", "between:5:10"] 272 | }; 273 | 274 | data = { 275 | email: "semmivp1@gmail.com", 276 | password: "secret" 277 | } 278 | 279 |
280 | ``` 281 | 282 | the key `email` at rules object will be used to find the value at data object to be validate. 283 | 284 | so in the above example the rule `required and email` in the `email` key at rules object will be validate against `semmivp1@gmail.com` in the `email` key at data object. 285 | 286 | ##### Using Parameters 287 | 288 | Some Validation rules need more arguments beside the `data` to be working properly, you can defining arguments in every rules type `Array|String|Object` by using separator `:` the first encounter `:` will be first arguments and so on and so forth. 289 | 290 | Example 291 | 292 | ```javascript 293 | // between validation 294 | { 295 | password: "between:5:10"; 296 | } 297 | ``` 298 | 299 | in above example 5 will be the first argument and define as variable `min` in our `between` validation rule and 10 will be the second argument and define as variable `max` in our `between` validation rule. 300 | 301 | ##### Rules As Array 302 | 303 | You can use Array as your rules collection like this 304 | 305 | ```javascript 306 | rules = { 307 | email: ["required", "email"], 308 | password: ["required", "between:5:10"] 309 | }; 310 | ``` 311 | 312 | ##### Rules As String 313 | 314 | You can use String as your rules collection like this. You can use multiple validation rule for one data by using separator `|` 315 | 316 | ```javascript 317 | rules = { 318 | email: "required|email", 319 | password: "required|between:5:10" 320 | }; 321 | ``` 322 | 323 | ##### Rules As Object and Custom error message 324 | 325 | By using Object you will have to define two key for the rule working properly the first one is `rule` that will tell what `rule` should be implemented and the other will be the message that will overwrite the default error message 326 | 327 | ###### Custom message For every fail rule 328 | 329 | ```javascript 330 | { 331 | email: { 332 | rules: ["required", "email"], 333 | message: "Please allow me to fill your inbox" 334 | } 335 | } 336 | ``` 337 | 338 | by using `String` as the `message` value that string will override every fail validation rules. 339 | 340 | ###### Custom message For detail fail rule 341 | 342 | ```javascript 343 | { 344 | password: { 345 | rules: "required|between:5:10", 346 | message: { 347 | required: "Allow yourself to come to our system", 348 | between: "Make yourself secure" 349 | } 350 | } 351 | } 352 | ``` 353 | 354 | by using `Object` as the `message` value the error message will be override the key provided by user. 355 | 356 | as default required error message is 357 | 358 | ``` 359 | field is required 360 | ``` 361 | 362 | but if you are using the custom message for the required (key) the error will be override with the given message. 363 | 364 | ``` 365 | Allow yourself to come to our system 366 | ``` 367 | 368 | ##### Available Rules 369 | 370 | ``` 371 | [key] is the given data name 372 | ``` 373 | 374 | say your validation rule is an object like this 375 | 376 | ```javascript 377 | rules = { 378 | email: ["required", "email"], 379 | password: ["required", "between:5:10"] 380 | }; 381 | ``` 382 | 383 | then if your email validation is failed in any rules that fail the `[key]` at yout error message will be replace with `email` 384 | 385 | | Name | Extra Parameter(s) | Default Error Message | Invalid Condition | 386 | | ------------- | --------------------------------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | 387 | | required |
- | [key] field is required | 1. empty array
2. Empty Object
3. `null` or empty data | 388 | | email |
- | [key] field is invalid email address | Invalid email address with regex validation | 389 | | number |
- | [key] field is not a numeric value | Not a Number `NaN` | 390 | | url |
- | [key] field is not a valid Url | Invalid Url with regex validation | 391 | | max |
{max} | [key] field cannot exceed {max} characters | Data `(string)` characters length is exceed the given maximum character | 392 | | min |
{min} | [key] field must be at least {min} characters | Data `(string)` characters length is less than the given minimun character | 393 | | lessThan |
{max} | [key] field must be less than {max} | Data `(number)` is greater than the given maximum number | 394 | | greaterThan |
{min} | [key] field must be greater than {max} | Data `(number)` is less than the given minimum number | 395 | | between |
{min}, {max} | [key] field must be at least {min} character and not exceed {max} characters | Data `(String)` characters length is less than the given minimum character and greater than the given maximum character | 396 | | date |
- | [key] field is an invalid Date | Invalid Date with javascript built in date validation | 397 | | ifExist |
{otherField} , {otherRule}, {...rest} | the error message will be the same like the {otherRule} validation message | When the {otherField} is exist and fail the {otherRule} validation rules. | 398 | | ifDoesntExist |
{otherField}, {otherRule}, {...rest} | the error message will be the same like the {otherRule} validation message | When the {otherField} is not exist and fail the {otherRule} validation rules. | 399 | | whenTrue |
{condition}, {otherRule}, {...rest} | the error message will be the same like the {otherRule} validation message | when the {condtion} given is `true as a String` and fail the {otherRule} validation rules | 400 | | whenFalse |
{condition}, {otherRule}, {...rest} | the error message will be the same like the {otherRule} validation message | when the {condtion} given is `false as a String` and fail the {otherRule} validation rules | 401 | | inArray | {arrays} | [key] is not included in this array {params} | Data given is not included in the {arrays} arguments. | 402 | | startsWith | {startWith} | [key] must be start with one of this characters {params} | Data given is not start with the given {startWith} arguments. | 403 | | endsWith | {endsWith} | [key] must be end with one of this characters {params} | Data given is not start with the given {endsWith} arguments. | 404 | 405 | ## To do 406 | 407 | - [] Writing Test 408 | 409 | ## Thanks to 410 | 411 | - Kent C Dods - [Advance React Component](https://egghead.io/courses/advanced-react-component-patterns) 412 | - Jeffrey Way - [Form Validation at vue](https://laracasts.com/series/learn-vue-2-step-by-step/episodes/19) 413 | - Laravel Validation - [Laravel Validation](https://laravel.com/docs/5.6/validation) 414 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | !(function(e, t) { 2 | for (var r in t) e[r] = t[r]; 3 | })( 4 | exports, 5 | (function(e) { 6 | var t = {}; 7 | function r(n) { 8 | if (t[n]) return t[n].exports; 9 | var i = (t[n] = { i: n, l: !1, exports: {} }); 10 | return e[n].call(i.exports, i, i.exports, r), (i.l = !0), i.exports; 11 | } 12 | return ( 13 | (r.m = e), 14 | (r.c = t), 15 | (r.d = function(e, t, n) { 16 | r.o(e, t) || Object.defineProperty(e, t, { enumerable: !0, get: n }); 17 | }), 18 | (r.r = function(e) { 19 | "undefined" != typeof Symbol && 20 | Symbol.toStringTag && 21 | Object.defineProperty(e, Symbol.toStringTag, { value: "Module" }), 22 | Object.defineProperty(e, "__esModule", { value: !0 }); 23 | }), 24 | (r.t = function(e, t) { 25 | if ((1 & t && (e = r(e)), 8 & t)) return e; 26 | if (4 & t && "object" == typeof e && e && e.__esModule) return e; 27 | var n = Object.create(null); 28 | if ( 29 | (r.r(n), 30 | Object.defineProperty(n, "default", { enumerable: !0, value: e }), 31 | 2 & t && "string" != typeof e) 32 | ) 33 | for (var i in e) 34 | r.d( 35 | n, 36 | i, 37 | function(t) { 38 | return e[t]; 39 | }.bind(null, i) 40 | ); 41 | return n; 42 | }), 43 | (r.n = function(e) { 44 | var t = 45 | e && e.__esModule 46 | ? function() { 47 | return e.default; 48 | } 49 | : function() { 50 | return e; 51 | }; 52 | return r.d(t, "a", t), t; 53 | }), 54 | (r.o = function(e, t) { 55 | return Object.prototype.hasOwnProperty.call(e, t); 56 | }), 57 | (r.p = ""), 58 | r((r.s = 9)) 59 | ); 60 | })([ 61 | function(e, t, r) { 62 | "use strict"; 63 | Object.defineProperty(t, "__esModule", { value: !0 }); 64 | var n = 65 | Object.assign || 66 | function(e) { 67 | for (var t = 1; t < arguments.length; t++) { 68 | var r = arguments[t]; 69 | for (var n in r) 70 | Object.prototype.hasOwnProperty.call(r, n) && (e[n] = r[n]); 71 | } 72 | return e; 73 | }, 74 | i = (function() { 75 | function e(e, t) { 76 | for (var r = 0; r < t.length; r++) { 77 | var n = t[r]; 78 | (n.enumerable = n.enumerable || !1), 79 | (n.configurable = !0), 80 | "value" in n && (n.writable = !0), 81 | Object.defineProperty(e, n.key, n); 82 | } 83 | } 84 | return function(t, r, n) { 85 | return r && e(t.prototype, r), n && e(t, n), t; 86 | }; 87 | })(); 88 | var a = (function() { 89 | function e() { 90 | !(function(e, t) { 91 | if (!(e instanceof t)) 92 | throw new TypeError("Cannot call a class as a function"); 93 | })(this, e), 94 | (this.errors = {}); 95 | } 96 | return ( 97 | i(e, [ 98 | { 99 | key: "record", 100 | value: function(e) { 101 | return (this.errors = e), this; 102 | } 103 | }, 104 | { 105 | key: "set", 106 | value: function(e, t) { 107 | return ( 108 | (this.errors = n( 109 | {}, 110 | this.errors, 111 | (function(e, t, r) { 112 | return ( 113 | t in e 114 | ? Object.defineProperty(e, t, { 115 | value: r, 116 | enumerable: !0, 117 | configurable: !0, 118 | writable: !0 119 | }) 120 | : (e[t] = r), 121 | e 122 | ); 123 | })({}, e, t) 124 | )), 125 | this 126 | ); 127 | } 128 | }, 129 | { 130 | key: "get", 131 | value: function(e) { 132 | return this.errors[e] || null; 133 | } 134 | }, 135 | { 136 | key: "has", 137 | value: function(e) { 138 | return !!this.errors[e]; 139 | } 140 | }, 141 | { 142 | key: "all", 143 | value: function() { 144 | return this.errors; 145 | } 146 | }, 147 | { 148 | key: "clear", 149 | value: function(e) { 150 | var t = this; 151 | return e instanceof Array 152 | ? (e.forEach(function(e) { 153 | return delete t.errors[e]; 154 | }), 155 | this) 156 | : e 157 | ? (delete this.errors[e], this) 158 | : ((this.errors = {}), this); 159 | } 160 | } 161 | ]), 162 | e 163 | ); 164 | })(); 165 | t.default = a; 166 | }, 167 | function(e, t, r) { 168 | "use strict"; 169 | Object.defineProperty(t, "__esModule", { value: !0 }); 170 | var n = (function() { 171 | function e(e, t) { 172 | for (var r = 0; r < t.length; r++) { 173 | var n = t[r]; 174 | (n.enumerable = n.enumerable || !1), 175 | (n.configurable = !0), 176 | "value" in n && (n.writable = !0), 177 | Object.defineProperty(e, n.key, n); 178 | } 179 | } 180 | return function(t, r, n) { 181 | return r && e(t.prototype, r), n && e(t, n), t; 182 | }; 183 | })(), 184 | i = o(r(5)), 185 | a = o(r(0)); 186 | function o(e) { 187 | return e && e.__esModule ? e : { default: e }; 188 | } 189 | var u = (function() { 190 | function e(t, r) { 191 | for (var n in ((function(e, t) { 192 | if (!(e instanceof t)) 193 | throw new TypeError("Cannot call a class as a function"); 194 | })(this, e), 195 | (this.originalData = t), 196 | (this.validationRules = r), 197 | t)) 198 | this[n] = t[n]; 199 | (this.validator = new i.default(t)), (this.errors = new a.default()); 200 | } 201 | return ( 202 | n(e, [ 203 | { 204 | key: "get", 205 | value: function(e) { 206 | return this[e]; 207 | } 208 | }, 209 | { 210 | key: "set", 211 | value: function(e, t) { 212 | this[e] = t; 213 | var r = this.validator.validate(this, this.validationRules); 214 | return ( 215 | r.validated 216 | ? this.errors.clear(e) 217 | : this.errors.record(r.error), 218 | this 219 | ); 220 | } 221 | }, 222 | { 223 | key: "validated", 224 | value: function() { 225 | return this.validator.valid; 226 | } 227 | }, 228 | { 229 | key: "setError", 230 | value: function(e) { 231 | return this.errors.record(e), this; 232 | } 233 | }, 234 | { 235 | key: "validate", 236 | value: function() { 237 | var e = this.validator.validate(this, this.validationRules); 238 | return { validated: e.validated, errors: e.error }; 239 | } 240 | } 241 | ]), 242 | e 243 | ); 244 | })(); 245 | t.default = u; 246 | }, 247 | function(e, t, r) { 248 | e.exports = r(7)(); 249 | }, 250 | function(e, t) { 251 | e.exports = require("react"); 252 | }, 253 | function(e, t, r) { 254 | "use strict"; 255 | Object.defineProperty(t, "__esModule", { value: !0 }); 256 | var n = (function() { 257 | function e(e, t) { 258 | for (var r = 0; r < t.length; r++) { 259 | var n = t[r]; 260 | (n.enumerable = n.enumerable || !1), 261 | (n.configurable = !0), 262 | "value" in n && (n.writable = !0), 263 | Object.defineProperty(e, n.key, n); 264 | } 265 | } 266 | return function(t, r, n) { 267 | return r && e(t.prototype, r), n && e(t, n), t; 268 | }; 269 | })(), 270 | i = r(3), 271 | a = (s(i), s(r(2))), 272 | o = s(r(1)), 273 | u = s(r(0)); 274 | function s(e) { 275 | return e && e.__esModule ? e : { default: e }; 276 | } 277 | function l(e, t, r) { 278 | return ( 279 | t in e 280 | ? Object.defineProperty(e, t, { 281 | value: r, 282 | enumerable: !0, 283 | configurable: !0, 284 | writable: !0 285 | }) 286 | : (e[t] = r), 287 | e 288 | ); 289 | } 290 | function f(e, t) { 291 | if (!e) 292 | throw new ReferenceError( 293 | "this hasn't been initialised - super() hasn't been called" 294 | ); 295 | return !t || ("object" != typeof t && "function" != typeof t) ? e : t; 296 | } 297 | var c = (function(e) { 298 | function t() { 299 | var e, r, n; 300 | !(function(e, t) { 301 | if (!(e instanceof t)) 302 | throw new TypeError("Cannot call a class as a function"); 303 | })(this, t); 304 | for (var i = arguments.length, a = Array(i), s = 0; s < i; s++) 305 | a[s] = arguments[s]; 306 | return ( 307 | (r = n = f( 308 | this, 309 | (e = t.__proto__ || Object.getPrototypeOf(t)).call.apply( 310 | e, 311 | [this].concat(a) 312 | ) 313 | )), 314 | (n.state = { isValid: !0, errors: new u.default() }), 315 | (n.onChange = function(e) { 316 | var t = n.props, 317 | r = t.name, 318 | i = t.rules, 319 | a = e.target.value; 320 | n.setState(function(e) { 321 | return { errors: e.errors.clear([r]) }; 322 | }); 323 | var u = new o.default(l({}, r, a), l({}, r, i)).validate(); 324 | u.validated || 325 | n.setState(function(e) { 326 | return { isValid: !1, errors: e.errors.record(u.errors) }; 327 | }), 328 | n.props.onChangeValue(a); 329 | }), 330 | f(n, r) 331 | ); 332 | } 333 | return ( 334 | (function(e, t) { 335 | if ("function" != typeof t && null !== t) 336 | throw new TypeError( 337 | "Super expression must either be null or a function, not " + 338 | typeof t 339 | ); 340 | (e.prototype = Object.create(t && t.prototype, { 341 | constructor: { 342 | value: e, 343 | enumerable: !1, 344 | writable: !0, 345 | configurable: !0 346 | } 347 | })), 348 | t && 349 | (Object.setPrototypeOf 350 | ? Object.setPrototypeOf(e, t) 351 | : (e.__proto__ = t)); 352 | })(t, i.Component), 353 | n(t, [ 354 | { 355 | key: "render", 356 | value: function() { 357 | var e = this.state, 358 | t = e.isValidate, 359 | r = e.errors; 360 | return this.props.children({ 361 | isValidate: t, 362 | errors: r, 363 | onChange: this.onChange 364 | }); 365 | } 366 | } 367 | ]), 368 | t 369 | ); 370 | })(); 371 | (c.propTypes = { 372 | value: a.default.oneOfType([a.default.string, a.default.number]), 373 | rules: a.default.string, 374 | name: a.default.string 375 | }), 376 | (t.default = c); 377 | }, 378 | function(e, t, r) { 379 | "use strict"; 380 | Object.defineProperty(t, "__esModule", { value: !0 }); 381 | var n = 382 | "function" == typeof Symbol && "symbol" == typeof Symbol.iterator 383 | ? function(e) { 384 | return typeof e; 385 | } 386 | : function(e) { 387 | return e && 388 | "function" == typeof Symbol && 389 | e.constructor === Symbol && 390 | e !== Symbol.prototype 391 | ? "symbol" 392 | : typeof e; 393 | }, 394 | i = (function() { 395 | return function(e, t) { 396 | if (Array.isArray(e)) return e; 397 | if (Symbol.iterator in Object(e)) 398 | return (function(e, t) { 399 | var r = [], 400 | n = !0, 401 | i = !1, 402 | a = void 0; 403 | try { 404 | for ( 405 | var o, u = e[Symbol.iterator](); 406 | !(n = (o = u.next()).done) && 407 | (r.push(o.value), !t || r.length !== t); 408 | n = !0 409 | ); 410 | } catch (e) { 411 | (i = !0), (a = e); 412 | } finally { 413 | try { 414 | !n && u.return && u.return(); 415 | } finally { 416 | if (i) throw a; 417 | } 418 | } 419 | return r; 420 | })(e, t); 421 | throw new TypeError( 422 | "Invalid attempt to destructure non-iterable instance" 423 | ); 424 | }; 425 | })(), 426 | a = (function() { 427 | function e(e, t) { 428 | for (var r = 0; r < t.length; r++) { 429 | var n = t[r]; 430 | (n.enumerable = n.enumerable || !1), 431 | (n.configurable = !0), 432 | "value" in n && (n.writable = !0), 433 | Object.defineProperty(e, n.key, n); 434 | } 435 | } 436 | return function(t, r, n) { 437 | return r && e(t.prototype, r), n && e(t, n), t; 438 | }; 439 | })(); 440 | function o(e, t, r) { 441 | return ( 442 | t in e 443 | ? Object.defineProperty(e, t, { 444 | value: r, 445 | enumerable: !0, 446 | configurable: !0, 447 | writable: !0 448 | }) 449 | : (e[t] = r), 450 | e 451 | ); 452 | } 453 | function u(e) { 454 | return Array.isArray(e) ? e : Array.from(e); 455 | } 456 | var s = (function() { 457 | function e(t) { 458 | !(function(e, t) { 459 | if (!(e instanceof t)) 460 | throw new TypeError("Cannot call a class as a function"); 461 | })(this, e), 462 | l.call(this), 463 | (this.form = t), 464 | (this.valid = !1), 465 | (this.rawForm = {}); 466 | } 467 | return ( 468 | a(e, [ 469 | { 470 | key: "required", 471 | value: function(e, t) { 472 | return e instanceof Array && 0 === e.length 473 | ? this.brokeValidation(t, "field is required", "required") 474 | : e instanceof Object && 0 === Object.keys(e).length 475 | ? this.brokeValidation(t, "field is required", "required") 476 | : e 477 | ? this.passValidation() 478 | : this.brokeValidation( 479 | t, 480 | "field is required", 481 | "required" 482 | ); 483 | } 484 | }, 485 | { 486 | key: "email", 487 | value: function(e, t) { 488 | return /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(e) 489 | ? this.passValidation() 490 | : this.brokeValidation( 491 | t, 492 | "field is invalid email address", 493 | "email" 494 | ); 495 | } 496 | }, 497 | { 498 | key: "number", 499 | value: function(e, t) { 500 | return isNaN(e) 501 | ? this.brokeValidation( 502 | t, 503 | "field is not a numeric value", 504 | "number" 505 | ) 506 | : this.passValidation(); 507 | } 508 | }, 509 | { 510 | key: "url", 511 | value: function(e, t) { 512 | return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( 513 | e 514 | ) 515 | ? this.passValidation() 516 | : this.brokeValidation( 517 | t, 518 | "field is not a valid Url", 519 | "url" 520 | ); 521 | } 522 | }, 523 | { 524 | key: "max", 525 | value: function(e, t, r) { 526 | var n = i(r, 1)[0]; 527 | return e.length <= n || !e 528 | ? this.passValidation() 529 | : this.brokeValidation( 530 | t, 531 | "field cannot exceed " + n + " characters", 532 | "max" 533 | ); 534 | } 535 | }, 536 | { 537 | key: "min", 538 | value: function(e, t, r) { 539 | var n = i(r, 1)[0]; 540 | return e.length >= n || !e 541 | ? this.passValidation() 542 | : this.brokeValidation( 543 | t, 544 | "field must be at least " + n + " characters", 545 | "min" 546 | ); 547 | } 548 | }, 549 | { 550 | key: "lessThan", 551 | value: function(e, t, r) { 552 | var n = i(r, 1)[0]; 553 | return parseInt(e) < parseInt(n) || !e 554 | ? this.passValidation() 555 | : this.brokeValidation( 556 | t, 557 | "field must be less than " + n, 558 | "lessThan" 559 | ); 560 | } 561 | }, 562 | { 563 | key: "greaterThan", 564 | value: function(e, t, r) { 565 | var n = i(r, 1)[0]; 566 | return parseInt(e) > parseInt(n) || !e 567 | ? this.passValidation() 568 | : this.brokeValidation( 569 | t, 570 | "field must be greater than " + n, 571 | "lessThan" 572 | ); 573 | } 574 | }, 575 | { 576 | key: "between", 577 | value: function(e, t, r) { 578 | var n = i(r, 2), 579 | a = n[0], 580 | o = n[1]; 581 | return e.length >= a && e.length <= o 582 | ? this.passValidation() 583 | : this.brokeValidation( 584 | t, 585 | "field must be at least " + 586 | a + 587 | " character and not exceed " + 588 | o + 589 | " characters", 590 | "between" 591 | ); 592 | } 593 | }, 594 | { 595 | key: "date", 596 | value: function(e, t) { 597 | return isNaN(Date.parse(e)) 598 | ? this.brokeValidation( 599 | t, 600 | "field is an invalid Date", 601 | "date" 602 | ) 603 | : this.passValidation(); 604 | } 605 | }, 606 | { 607 | key: "ifExist", 608 | value: function(e, t, r) { 609 | var n = u(r), 610 | i = n[0], 611 | a = n[1], 612 | o = n.slice(2); 613 | return this.rawForm[i] 614 | ? this[a](e, t, o) 615 | : this.passValidation(); 616 | } 617 | }, 618 | { 619 | key: "ifDoesntExist", 620 | value: function(e, t, r) { 621 | var n = u(r), 622 | i = n[0], 623 | a = n[1], 624 | o = n.slice(2); 625 | return this.rawForm[i] 626 | ? this.passValidation() 627 | : this[a](e, t, o); 628 | } 629 | }, 630 | { 631 | key: "whenTrue", 632 | value: function(e, t, r) { 633 | var n = u(r), 634 | i = n[0], 635 | a = n[1], 636 | o = n.slice(2); 637 | return "true" === i 638 | ? this[a](e, t, o) 639 | : this.passValidation(); 640 | } 641 | }, 642 | { 643 | key: "whenFalse", 644 | value: function(e, t, r) { 645 | var n = u(r), 646 | i = n[0], 647 | a = n[1], 648 | o = n.slice(2); 649 | return "false" === i 650 | ? this[a](e, t, o) 651 | : this.passValidation(); 652 | } 653 | }, 654 | { 655 | key: "inArray", 656 | value: function(e, t, r) { 657 | return r.includes(e) 658 | ? this.passValidation() 659 | : this.brokeValidation( 660 | t, 661 | "is not included in this array " + r, 662 | "in" 663 | ); 664 | } 665 | }, 666 | { 667 | key: "startsWith", 668 | value: function(e, t, r) { 669 | return 0 !== 670 | r.filter(function(t) { 671 | return t.startsWith(e); 672 | }).length 673 | ? this.passValidation() 674 | : this.brokeValidation( 675 | t, 676 | "must be start with one of this word " + r, 677 | "endsWith" 678 | ); 679 | } 680 | }, 681 | { 682 | key: "endsWith", 683 | value: function(e, t, r) { 684 | return 0 !== 685 | r.filter(function(t) { 686 | return t.endsWith(e); 687 | }).length 688 | ? this.passValidation() 689 | : this.brokeValidation( 690 | t, 691 | "must be end with one of this word " + r, 692 | "endsWith" 693 | ); 694 | } 695 | }, 696 | { 697 | key: "passValidation", 698 | value: function() { 699 | return { error: !1 }; 700 | } 701 | }, 702 | { 703 | key: "brokeValidation", 704 | value: function(e, t, r) { 705 | var n, 706 | i, 707 | a = this.rawForm.validationRules[e].message, 708 | u = e + " " + t; 709 | return a 710 | ? (o( 711 | (i = { error: !0 }), 712 | e, 713 | this.isString(a) ? a : a[r] || u 714 | ), 715 | o(i, "key", e), 716 | o(i, "type", r), 717 | i) 718 | : (o((n = { error: !0 }), e, u), 719 | o(n, "key", e), 720 | o(n, "type", r), 721 | n); 722 | } 723 | }, 724 | { 725 | key: "toObject", 726 | value: function(e) { 727 | var t = this; 728 | return e.reduce(function(e, r) { 729 | return ( 730 | t.isString(t.rawForm.validationRules[r.key].message) 731 | ? (e[r.key] = r[r.key]) 732 | : e[r.key] 733 | ? (e[r.key] = e[r.key] + ", " + r[r.key]) 734 | : (e[r.key] = r[r.key]), 735 | e 736 | ); 737 | }, {}); 738 | } 739 | }, 740 | { 741 | key: "validate", 742 | value: function(e, t) { 743 | var r = this; 744 | this.rawForm = e; 745 | var n = Object.keys(t) 746 | .reduce(function(n, i) { 747 | var a = t[i]; 748 | if (r.isString(a)) { 749 | var o = r.stringValidationhandler(e, a, i); 750 | n = n.concat(o); 751 | } else if (r.isObject(a)) { 752 | var u = a.rules; 753 | if (r.isArray(u)) { 754 | var s = r.mapRules(e, u, i); 755 | n = n.concat(s); 756 | } 757 | if (r.isString(u)) { 758 | var l = r.stringValidationhandler(e, u, i); 759 | n = n.concat(l); 760 | } 761 | } else if (r.isArray(a)) { 762 | var f = r.mapRules(e, a, i); 763 | n = n.concat(f); 764 | } 765 | return n; 766 | }, []) 767 | .filter(function(e) { 768 | return e.error; 769 | }); 770 | return 0 === n.length 771 | ? ((this.valid = !0), { validated: !0 }) 772 | : ((this.valid = !1), 773 | { validated: !1, error: this.toObject(n) }); 774 | } 775 | } 776 | ]), 777 | e 778 | ); 779 | })(), 780 | l = function() { 781 | var e = this; 782 | (this.stringValidationhandler = function(t, r, n) { 783 | var i = []; 784 | if (r.includes("|")) { 785 | var a = e.mapRules(t, r.split("|"), n); 786 | i = i.concat(a); 787 | } else i = i.concat(e.singleRules(t, r, n)); 788 | return i; 789 | }), 790 | (this.mapRules = function(t, r, n) { 791 | return r.map(function(r) { 792 | if (r.includes(":")) { 793 | var i = u(r.split(":")), 794 | a = i[0], 795 | o = i.slice(1); 796 | return e[a](t[n], n, o); 797 | } 798 | return e[r](t[n], n); 799 | }); 800 | }), 801 | (this.singleRules = function(t, r, n) { 802 | var i = []; 803 | if (r.includes(":")) { 804 | var a = u(r.split(":")), 805 | o = a[0], 806 | s = a.slice(1); 807 | i = i.concat(e[o](t[n], n, s)); 808 | } else i = i.concat(e[r](t[n], n)); 809 | return i; 810 | }), 811 | (this.isString = function(e) { 812 | return "string" == typeof e; 813 | }), 814 | (this.isArray = function(e) { 815 | return ( 816 | "object" === (void 0 === e ? "undefined" : n(e)) && 817 | Array.isArray(e) 818 | ); 819 | }), 820 | (this.isObject = function(e) { 821 | return ( 822 | "object" === (void 0 === e ? "undefined" : n(e)) && 823 | !Array.isArray(e) 824 | ); 825 | }); 826 | }; 827 | t.default = s; 828 | }, 829 | function(e, t, r) { 830 | "use strict"; 831 | e.exports = "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"; 832 | }, 833 | function(e, t, r) { 834 | "use strict"; 835 | var n = r(6); 836 | function i() {} 837 | e.exports = function() { 838 | function e(e, t, r, i, a, o) { 839 | if (o !== n) { 840 | var u = new Error( 841 | "Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types" 842 | ); 843 | throw ((u.name = "Invariant Violation"), u); 844 | } 845 | } 846 | function t() { 847 | return e; 848 | } 849 | e.isRequired = e; 850 | var r = { 851 | array: e, 852 | bool: e, 853 | func: e, 854 | number: e, 855 | object: e, 856 | string: e, 857 | symbol: e, 858 | any: e, 859 | arrayOf: t, 860 | element: e, 861 | instanceOf: t, 862 | node: e, 863 | objectOf: t, 864 | oneOf: t, 865 | oneOfType: t, 866 | shape: t, 867 | exact: t 868 | }; 869 | return (r.checkPropTypes = i), (r.PropTypes = r), r; 870 | }; 871 | }, 872 | function(e, t, r) { 873 | "use strict"; 874 | Object.defineProperty(t, "__esModule", { value: !0 }); 875 | var n = 876 | Object.assign || 877 | function(e) { 878 | for (var t = 1; t < arguments.length; t++) { 879 | var r = arguments[t]; 880 | for (var n in r) 881 | Object.prototype.hasOwnProperty.call(r, n) && (e[n] = r[n]); 882 | } 883 | return e; 884 | }, 885 | i = (function() { 886 | return function(e, t) { 887 | if (Array.isArray(e)) return e; 888 | if (Symbol.iterator in Object(e)) 889 | return (function(e, t) { 890 | var r = [], 891 | n = !0, 892 | i = !1, 893 | a = void 0; 894 | try { 895 | for ( 896 | var o, u = e[Symbol.iterator](); 897 | !(n = (o = u.next()).done) && 898 | (r.push(o.value), !t || r.length !== t); 899 | n = !0 900 | ); 901 | } catch (e) { 902 | (i = !0), (a = e); 903 | } finally { 904 | try { 905 | !n && u.return && u.return(); 906 | } finally { 907 | if (i) throw a; 908 | } 909 | } 910 | return r; 911 | })(e, t); 912 | throw new TypeError( 913 | "Invalid attempt to destructure non-iterable instance" 914 | ); 915 | }; 916 | })(), 917 | a = (function() { 918 | function e(e, t) { 919 | for (var r = 0; r < t.length; r++) { 920 | var n = t[r]; 921 | (n.enumerable = n.enumerable || !1), 922 | (n.configurable = !0), 923 | "value" in n && (n.writable = !0), 924 | Object.defineProperty(e, n.key, n); 925 | } 926 | } 927 | return function(t, r, n) { 928 | return r && e(t.prototype, r), n && e(t, n), t; 929 | }; 930 | })(), 931 | o = r(3), 932 | u = (f(o), f(r(2))), 933 | s = f(r(1)), 934 | l = f(r(0)); 935 | function f(e) { 936 | return e && e.__esModule ? e : { default: e }; 937 | } 938 | function c(e, t) { 939 | if (!e) 940 | throw new ReferenceError( 941 | "this hasn't been initialised - super() hasn't been called" 942 | ); 943 | return !t || ("object" != typeof t && "function" != typeof t) ? e : t; 944 | } 945 | var d = (function(e) { 946 | function t() { 947 | var e, r, a; 948 | !(function(e, t) { 949 | if (!(e instanceof t)) 950 | throw new TypeError("Cannot call a class as a function"); 951 | })(this, t); 952 | for (var o = arguments.length, u = Array(o), f = 0; f < o; f++) 953 | u[f] = arguments[f]; 954 | return ( 955 | (r = a = c( 956 | this, 957 | (e = t.__proto__ || Object.getPrototypeOf(t)).call.apply( 958 | e, 959 | [this].concat(u) 960 | ) 961 | )), 962 | (a.toArray = function(e) { 963 | return Object.entries(e).reduce(function(e, t) { 964 | var r = i(t, 2), 965 | n = r[0], 966 | a = r[1]; 967 | return e.concat({ key: n, value: a }); 968 | }, []); 969 | }), 970 | (a.state = { 971 | isValid: !0, 972 | errors: new l.default(), 973 | data: a.toArray(a.props.data) 974 | }), 975 | (a.onChange = function(e) { 976 | return function(t) { 977 | console.log(e); 978 | var r = t.target.value; 979 | a.props.validateOnChange && 980 | a.validate( 981 | (function(e, t, r) { 982 | return ( 983 | t in e 984 | ? Object.defineProperty(e, t, { 985 | value: r, 986 | enumerable: !0, 987 | configurable: !0, 988 | writable: !0 989 | }) 990 | : (e[t] = r), 991 | e 992 | ); 993 | })({}, e, r) 994 | ), 995 | a.props.onChangeValue && a.props.onChangeValue(e, r); 996 | }; 997 | }), 998 | (a.onSubmit = function(e) { 999 | console.log("test"); 1000 | var t = a.validate(); 1001 | a.props.onSubmit && a.props.onSubmit(e, t.validated); 1002 | }), 1003 | (a.validate = function(e) { 1004 | var t = a.props, 1005 | r = t.rules, 1006 | i = t.data, 1007 | o = n({}, i, e), 1008 | u = new s.default(o, n({}, r)).validate(); 1009 | if (!u.validated) 1010 | return ( 1011 | a.setState(function(e) { 1012 | return { isValid: !1, errors: e.errors.record(u.errors) }; 1013 | }), 1014 | u 1015 | ); 1016 | }), 1017 | c(a, r) 1018 | ); 1019 | } 1020 | return ( 1021 | (function(e, t) { 1022 | if ("function" != typeof t && null !== t) 1023 | throw new TypeError( 1024 | "Super expression must either be null or a function, not " + 1025 | typeof t 1026 | ); 1027 | (e.prototype = Object.create(t && t.prototype, { 1028 | constructor: { 1029 | value: e, 1030 | enumerable: !1, 1031 | writable: !0, 1032 | configurable: !0 1033 | } 1034 | })), 1035 | t && 1036 | (Object.setPrototypeOf 1037 | ? Object.setPrototypeOf(e, t) 1038 | : (e.__proto__ = t)); 1039 | })(t, o.Component), 1040 | a(t, [ 1041 | { 1042 | key: "render", 1043 | value: function() { 1044 | var e = this.state, 1045 | t = e.isValidate, 1046 | r = e.errors, 1047 | n = e.data; 1048 | return this.props.children({ 1049 | isValidate: t, 1050 | errors: r, 1051 | onChange: this.onChange, 1052 | data: n, 1053 | onSubmit: this.onSubmit 1054 | }); 1055 | } 1056 | } 1057 | ]), 1058 | t 1059 | ); 1060 | })(); 1061 | (d.propTypes = { 1062 | data: u.default.object, 1063 | rules: u.default.oneOfType([ 1064 | u.default.string, 1065 | u.default.object, 1066 | u.default.array 1067 | ]), 1068 | name: u.default.string 1069 | }), 1070 | (d.defaultProps = { validateOnChange: !0 }), 1071 | (t.default = d); 1072 | }, 1073 | function(e, t, r) { 1074 | "use strict"; 1075 | Object.defineProperty(t, "__esModule", { value: !0 }), (t.Form = void 0); 1076 | var n = i(r(8)); 1077 | i(r(4)); 1078 | function i(e) { 1079 | return e && e.__esModule ? e : { default: e }; 1080 | } 1081 | t.Form = n.default; 1082 | } 1083 | ]) 1084 | ); 1085 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.4.1", 7 | "react-dom": "^16.4.1", 8 | "react-form-validation-render-props": "0.1.7" 9 | }, 10 | "devDependencies": { 11 | "react-scripts": "1.1.4" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/semmiverian/react-form-validation/6e998e8f5cb99823564fced77f44c878fe16566b/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /example/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Form } from "react-form-validation-render-props"; 3 | // import { Form } from "./dist/index"; 4 | 5 | class App extends Component { 6 | state = { 7 | email: "", 8 | password: "" 9 | }; 10 | 11 | // rules using array 12 | // rules = { 13 | // email: ["required", "email"], 14 | // password: ["required", "between:5:10"] 15 | // }; 16 | 17 | // rules using string 18 | // rules = { 19 | // email: "required|email", 20 | // password: "required|between:5:10" 21 | // }; 22 | 23 | // rules using object and custom global message and custom message 24 | // errror ketika custom message nya cuman 1 padahal ada dua validation yang failed 25 | rules = { 26 | email: { 27 | rules: ["required", "email"], 28 | message: "Please allow me to filling your inbox" 29 | }, 30 | password: { 31 | rules: "required|between:5:10", 32 | message: { 33 | required: "Allow yourself to come to our system" 34 | // between: "Make yourself secure" 35 | } 36 | } 37 | }; 38 | 39 | onChangeValue = (key, value) => { 40 | this.setState({ [key]: value }); 41 | }; 42 | 43 | onSubmit = (e, valid) => { 44 | e.preventDefault(); 45 | }; 46 | 47 | render() { 48 | return ( 49 |
50 |
56 | {({ isValidate, errors, onChange, data, onSubmit }) => { 57 | return ( 58 | 59 | {data.map(item => { 60 | console.log(item.key); 61 | return ( 62 |
63 | 64 | 65 | {!isValidate && ( 66 | 67 | {errors.get(item.key)} 68 | 69 | )} 70 |
71 | ); 72 | })} 73 | 74 |
75 | ); 76 | }} 77 | 78 |
79 | ); 80 | } 81 | } 82 | 83 | export default App; 84 | -------------------------------------------------------------------------------- /example/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | 5 | it("renders without crashing", () => { 6 | const div = document.createElement("div"); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import registerServiceWorker from "./registerServiceWorker"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | registerServiceWorker(); 8 | -------------------------------------------------------------------------------- /example/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === "localhost" || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === "[::1]" || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener("load", () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | "This web app is being served cache-first by a service " + 44 | "worker. To learn more, visit https://goo.gl/SC7cgQ" 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === "installed") { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log("New content is available; please refresh."); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log("Content is cached for offline use."); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error("Error during service worker registration:", error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get("content-type").indexOf("javascript") === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | "No internet connection found. App is running in offline mode." 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ("serviceWorker" in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-form-validation-render-props", 3 | "version": "0.1.12", 4 | "description": "Form Validation for React using render props", 5 | "main": "./dist/index.js", 6 | "scripts": { 7 | "test": "jest ", 8 | "test:watch": "jest --watch", 9 | "prepublish": "rm -rf ./dist && npm run build", 10 | "precommit": "npm run test && pretty-quick --staged", 11 | "build": "webpack --config webpack.config.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/semmiverian/react-form-validation.git" 16 | }, 17 | "keywords": [ 18 | "react", 19 | "form", 20 | "validation" 21 | ], 22 | "author": "Semmi Verian ", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/semmiverian/react-form-validation/issues" 26 | }, 27 | "homepage": "https://github.com/semmiverian/react-form-validation#readme", 28 | "peerDependencies": { 29 | "react": ">=15.0.0", 30 | "react-dom": ">=15.0.0" 31 | }, 32 | "dependencies": { 33 | "prop-types": "15.6.2" 34 | }, 35 | "devDependencies": { 36 | "babel-core": "6.26.3", 37 | "babel-loader": "7.1.4", 38 | "babel-plugin-transform-class-properties": "6.24.1", 39 | "babel-plugin-transform-object-rest-spread": "6.26.0", 40 | "babel-preset-env": "1.7.0", 41 | "babel-preset-react": "6.24.1", 42 | "husky": "0.14.3", 43 | "jest": "23.2.0", 44 | "prettier": "1.13.5", 45 | "pretty-quick": "1.6.0", 46 | "react": "16.4.1", 47 | "react-dom": "16.4.1", 48 | "react-testing-library": "4.1.2", 49 | "webpack": "4.12.0", 50 | "webpack-cli": "3.0.8" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Form.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import FormUtils from "./utils/index"; 4 | import Error from "./utils/Error"; 5 | 6 | export default class Form extends Component { 7 | toArray = object => { 8 | return Object.entries(object).reduce((carry, [key, value]) => { 9 | return carry.concat({ 10 | key, 11 | value 12 | }); 13 | }, []); 14 | }; 15 | 16 | state = { 17 | isValid: true, 18 | errors: new Error(), 19 | data: this.toArray(this.props.data) 20 | }; 21 | 22 | static propTypes = { 23 | data: PropTypes.object, 24 | rules: PropTypes.oneOfType([ 25 | PropTypes.string, 26 | PropTypes.object, 27 | PropTypes.array 28 | ]), 29 | name: PropTypes.string 30 | }; 31 | 32 | static defaultProps = { 33 | validateOnChange: true 34 | }; 35 | 36 | onChange = key => e => { 37 | console.log(key); 38 | const value = e.target.value; 39 | 40 | if (this.props.validateOnChange) { 41 | this.validate({ [key]: value }); 42 | } 43 | 44 | if (this.props.onChangeValue) { 45 | this.props.onChangeValue(key, value); 46 | } 47 | }; 48 | 49 | onSubmit = e => { 50 | console.log("test"); 51 | const validation = this.validate(); 52 | 53 | if (this.props.onSubmit) { 54 | this.props.onSubmit(e, validation.validated); 55 | } 56 | }; 57 | 58 | validate = overrideData => { 59 | const { rules, data } = this.props; 60 | const override = { ...data, ...overrideData }; 61 | 62 | const form = new FormUtils(override, { ...rules }); 63 | const validation = form.validate(); 64 | 65 | if (!validation.validated) { 66 | this.setState(({ errors }) => { 67 | return { 68 | isValid: false, 69 | errors: errors.record(validation.errors) 70 | }; 71 | }); 72 | 73 | return validation; 74 | } 75 | }; 76 | 77 | render() { 78 | const { isValidate, errors, data } = this.state; 79 | 80 | return this.props.children({ 81 | isValidate, 82 | errors, 83 | onChange: this.onChange, 84 | data, 85 | onSubmit: this.onSubmit 86 | }); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Form.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render, cleanup, fireEvent } from "react-testing-library"; 3 | import Form from "./Form"; 4 | 5 | const FormComponent = (data, rules, extraProps) => { 6 | return ( 7 |
13 | {({ isValidate, errors, onChange, data, onSubmit }) => { 14 | return ( 15 | 16 | {data.map(item => { 17 | return ( 18 |
19 | 20 | 25 | {!isValidate && ( 26 | 27 | {errors.get(item.key)} 28 | 29 | )} 30 |
31 | ); 32 | })} 33 | 34 |
35 | ); 36 | }} 37 | 38 | ); 39 | }; 40 | 41 | afterEach(cleanup); 42 | 43 | it("should run on submit when onSubmit children function is trigger", () => { 44 | const data = { 45 | email: "", 46 | password: "" 47 | }; 48 | 49 | const rules = { 50 | email: "required", 51 | password: "required" 52 | }; 53 | 54 | const onSubmit = jest.fn(); 55 | 56 | const { getByText, getByTestId } = render( 57 | FormComponent(data, rules, { onSubmit }) 58 | ); 59 | 60 | fireEvent.submit(getByTestId("form")); 61 | 62 | expect(onSubmit).toHaveBeenCalledTimes(1); 63 | }); 64 | 65 | it("should show an error message when the validation is failed whenever onSubmit is triggered with invalid data", () => { 66 | const data = { email: "", password: "" }; 67 | 68 | const rules = { email: "required", password: "required" }; 69 | 70 | const onSubmit = jest.fn(); 71 | 72 | const { getAllByTestId, getByTestId } = render( 73 | FormComponent(data, rules, { 74 | onSubmit 75 | }) 76 | ); 77 | 78 | fireEvent.submit(getByTestId("form")); 79 | 80 | expect(getAllByTestId("error-message").length).toBe(Object.keys(data).length); 81 | }); 82 | 83 | it("should run onChange whenever we change the value and trigger the function", () => { 84 | const data = { email: "", password: "" }; 85 | 86 | const rules = { email: "required", password: "required" }; 87 | 88 | const onChangeValue = jest.fn(); 89 | 90 | const { getByTestId } = render(FormComponent(data, rules, { onChangeValue })); 91 | 92 | const email = getByTestId("email"); 93 | const password = getByTestId("password"); 94 | 95 | email.value = "semmivp1@gmail.com"; 96 | password.value = "super-secret"; 97 | 98 | fireEvent.change(email); 99 | fireEvent.change(password); 100 | 101 | expect(onChangeValue).toHaveBeenCalledTimes(2); 102 | expect(onChangeValue).toHaveBeenNthCalledWith(1, "email", email.value); 103 | expect(onChangeValue).toHaveBeenLastCalledWith("password", password.value); 104 | }); 105 | -------------------------------------------------------------------------------- /src/Input.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import Form from "./utils/index"; 4 | import Error from "./utils/Error"; 5 | 6 | export default class Input extends Component { 7 | state = { 8 | isValid: true, 9 | errors: new Error() 10 | }; 11 | 12 | static propTypes = { 13 | value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 14 | rules: PropTypes.string, 15 | name: PropTypes.string 16 | }; 17 | 18 | onChange = e => { 19 | const { name, rules } = this.props; 20 | const value = e.target.value; 21 | 22 | this.setState(({ errors }) => ({ errors: errors.clear([name]) })); 23 | const form = new Form({ [name]: value }, { [name]: rules }); 24 | const validation = form.validate(); 25 | 26 | if (!validation.validated) { 27 | this.setState(({ errors }) => { 28 | return { 29 | isValid: false, 30 | errors: errors.record(validation.errors) 31 | }; 32 | }); 33 | } 34 | 35 | this.props.onChangeValue(value); 36 | }; 37 | 38 | render() { 39 | const { isValidate, errors } = this.state; 40 | 41 | return this.props.children({ isValidate, errors, onChange: this.onChange }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Form from "./Form"; 2 | import Input from "./Input"; 3 | 4 | export { Form }; 5 | -------------------------------------------------------------------------------- /src/utils/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/semmiverian/react-form-validation/6e998e8f5cb99823564fced77f44c878fe16566b/src/utils/.DS_Store -------------------------------------------------------------------------------- /src/utils/Error.js: -------------------------------------------------------------------------------- 1 | export default class Error { 2 | constructor() { 3 | this.errors = {}; 4 | } 5 | 6 | record(errors) { 7 | this.errors = errors; 8 | 9 | return this; 10 | } 11 | 12 | set(key, val) { 13 | this.errors = { 14 | ...this.errors, 15 | [key]: val 16 | }; 17 | 18 | return this; 19 | } 20 | 21 | get(field) { 22 | return this.errors[field] || null; 23 | } 24 | 25 | has(field) { 26 | return !!this.errors[field]; 27 | } 28 | 29 | all() { 30 | return this.errors; 31 | } 32 | 33 | clear(field) { 34 | if (field instanceof Array) { 35 | field.forEach(field => delete this.errors[field]); 36 | 37 | return this; 38 | } 39 | 40 | if (field) { 41 | delete this.errors[field]; 42 | 43 | return this; 44 | } 45 | 46 | this.errors = {}; 47 | 48 | return this; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/Validate.js: -------------------------------------------------------------------------------- 1 | export default class Validator { 2 | constructor(form) { 3 | this.form = form; 4 | this.valid = false; 5 | this.rawForm = {}; 6 | } 7 | 8 | required(data, key) { 9 | if (data instanceof Array && data.length === 0) { 10 | return this.brokeValidation(key, "field is required", "required"); 11 | } 12 | 13 | if (data instanceof Object && Object.keys(data).length === 0) { 14 | return this.brokeValidation(key, "field is required", "required"); 15 | } 16 | 17 | if (!!data) { 18 | return this.passValidation(); 19 | } 20 | 21 | return this.brokeValidation(key, "field is required", "required"); 22 | } 23 | 24 | email(data, key) { 25 | if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(data)) { 26 | return this.brokeValidation( 27 | key, 28 | "field is invalid email address", 29 | "email" 30 | ); 31 | } 32 | 33 | return this.passValidation(); 34 | } 35 | 36 | number(data, key) { 37 | if (!isNaN(data)) { 38 | return this.passValidation(); 39 | } 40 | return this.brokeValidation(key, "field is not a numeric value", "number"); 41 | } 42 | 43 | url(data, key) { 44 | const check = /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( 45 | data 46 | ); 47 | // const check = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/.test(data) 48 | 49 | if (check) { 50 | return this.passValidation(); 51 | } 52 | 53 | return this.brokeValidation(key, "field is not a valid Url", "url"); 54 | } 55 | 56 | max(data, key, params) { 57 | const [max] = params; 58 | if (data.length <= max || !data) { 59 | return this.passValidation(); 60 | } 61 | 62 | return this.brokeValidation( 63 | key, 64 | `field cannot exceed ${max} characters`, 65 | "max" 66 | ); 67 | } 68 | 69 | min(data, key, params) { 70 | const [min] = params; 71 | if (data.length >= min || !data) { 72 | return this.passValidation(); 73 | } 74 | 75 | return this.brokeValidation( 76 | key, 77 | `field must be at least ${min} characters`, 78 | "min" 79 | ); 80 | } 81 | 82 | lessThan(data, key, params) { 83 | const [max] = params; 84 | 85 | if (parseInt(data) < parseInt(max) || !data) { 86 | return this.passValidation(); 87 | } 88 | 89 | return this.brokeValidation( 90 | key, 91 | `field must be less than ${max}`, 92 | "lessThan" 93 | ); 94 | } 95 | 96 | greaterThan(data, key, params) { 97 | const [min] = params; 98 | 99 | if (parseInt(data) > parseInt(min) || !data) { 100 | return this.passValidation(); 101 | } 102 | 103 | return this.brokeValidation( 104 | key, 105 | `field must be greater than ${min}`, 106 | "lessThan" 107 | ); 108 | } 109 | 110 | between(data, key, params) { 111 | const [min, max] = params; 112 | 113 | if (data.length >= min && data.length <= max) { 114 | return this.passValidation(); 115 | } 116 | 117 | return this.brokeValidation( 118 | key, 119 | `field must be at least ${min} character and not exceed ${max} characters`, 120 | "between" 121 | ); 122 | } 123 | 124 | date(data, key) { 125 | if (!isNaN(Date.parse(data))) { 126 | return this.passValidation(); 127 | } 128 | 129 | return this.brokeValidation(key, "field is an invalid Date", "date"); 130 | } 131 | 132 | ifExist(data, key, params) { 133 | const [comparison, otherRule, ...rest] = params; 134 | 135 | if (this.rawForm[comparison]) { 136 | return this[otherRule](data, key, rest); 137 | } 138 | 139 | return this.passValidation(); 140 | } 141 | 142 | ifDoesntExist(data, key, params) { 143 | const [comparison, otherRule, ...rest] = params; 144 | 145 | if (!this.rawForm[comparison]) { 146 | return this[otherRule](data, key, rest); 147 | } 148 | 149 | return this.passValidation(); 150 | } 151 | 152 | whenTrue(data, key, params) { 153 | const [whenCondition, otherRule, ...rest] = params; 154 | 155 | if (whenCondition === "true") { 156 | return this[otherRule](data, key, rest); 157 | } 158 | 159 | return this.passValidation(); 160 | } 161 | 162 | whenFalse(data, key, params) { 163 | const [whenCondition, otherRule, ...rest] = params; 164 | 165 | if (whenCondition === "false") { 166 | return this[otherRule](data, key, rest); 167 | } 168 | 169 | return this.passValidation(); 170 | } 171 | 172 | inArray(data, key, params) { 173 | if (params.includes(data)) { 174 | return this.passValidation(); 175 | } 176 | 177 | return this.brokeValidation( 178 | key, 179 | `is not included in this array ${params}`, 180 | "in" 181 | ); 182 | } 183 | 184 | startsWith(data, key, params) { 185 | if (params.filter(param => param.startsWith(data)).length !== 0) { 186 | return this.passValidation(); 187 | } 188 | 189 | return this.brokeValidation( 190 | key, 191 | `must be start with one of this word ${params}`, 192 | "endsWith" 193 | ); 194 | } 195 | 196 | endsWith(data, key, params) { 197 | if (params.filter(param => param.endsWith(data)).length !== 0) { 198 | return this.passValidation(); 199 | } 200 | 201 | return this.brokeValidation( 202 | key, 203 | `must be end with one of this word ${params}`, 204 | "endsWith" 205 | ); 206 | } 207 | 208 | passValidation() { 209 | return { error: false }; 210 | } 211 | 212 | brokeValidation(key, message, type) { 213 | const customMessage = this.rawForm.validationRules[key].message; 214 | const defaultMessage = `${key} ${message}`; 215 | 216 | if (customMessage) { 217 | const errorMessage = this.isString(customMessage) 218 | ? customMessage 219 | : customMessage[type] || defaultMessage; 220 | 221 | return { error: true, [key]: errorMessage, key, type }; 222 | } 223 | return { error: true, [key]: defaultMessage, key, type }; 224 | } 225 | 226 | toObject(errors) { 227 | return errors.reduce((carry, item) => { 228 | const isUsingGlobalCustomMessage = this.isString( 229 | this.rawForm.validationRules[item.key].message 230 | ); 231 | 232 | if (isUsingGlobalCustomMessage) { 233 | carry[item.key] = item[item.key]; 234 | } else if (carry[item.key]) { 235 | carry[item.key] = carry[item.key] + ", " + item[item.key]; 236 | } else { 237 | carry[item.key] = item[item.key]; 238 | } 239 | 240 | return carry; 241 | }, {}); 242 | } 243 | 244 | validate(form, rule) { 245 | this.rawForm = form; 246 | const errors = Object.keys(rule).reduce((carry, key) => { 247 | const validationRule = rule[key]; 248 | 249 | if (this.isString(validationRule)) { 250 | const validationData = this.stringValidationhandler( 251 | form, 252 | validationRule, 253 | key 254 | ); 255 | 256 | carry = carry.concat(validationData); 257 | } else if (this.isObject(validationRule)) { 258 | const rules = validationRule.rules; 259 | if (this.isArray(rules)) { 260 | const validationData = this.mapRules(form, rules, key); 261 | 262 | carry = carry.concat(validationData); 263 | } 264 | 265 | if (this.isString(rules)) { 266 | const validationData = this.stringValidationhandler(form, rules, key); 267 | 268 | carry = carry.concat(validationData); 269 | } 270 | } else if (this.isArray(validationRule)) { 271 | const validationData = this.mapRules(form, validationRule, key); 272 | 273 | carry = carry.concat(validationData); 274 | } 275 | 276 | return carry; 277 | }, []); 278 | 279 | const filterError = errors.filter(item => item.error); 280 | 281 | if (filterError.length === 0) { 282 | this.valid = true; 283 | return { validated: true }; 284 | } 285 | 286 | this.valid = false; 287 | return { validated: false, error: this.toObject(filterError) }; 288 | } 289 | 290 | stringValidationhandler = (form, rule, item) => { 291 | let carry = []; 292 | if (rule.includes("|")) { 293 | const splitData = this.mapRules(form, rule.split("|"), item); 294 | 295 | carry = carry.concat(splitData); 296 | } else { 297 | carry = carry.concat(this.singleRules(form, rule, item)); 298 | } 299 | 300 | return carry; 301 | }; 302 | 303 | mapRules = (form, rules, key) => { 304 | return rules.map(rule => { 305 | if (rule.includes(":")) { 306 | const [name, ...params] = rule.split(":"); 307 | return this[name](form[key], key, params); 308 | } 309 | return this[rule](form[key], key); 310 | }); 311 | }; 312 | 313 | singleRules = (form, rule, key) => { 314 | let carry = []; 315 | 316 | if (rule.includes(":")) { 317 | const [name, ...params] = rule.split(":"); 318 | carry = carry.concat(this[name](form[key], key, params)); 319 | } else { 320 | carry = carry.concat(this[rule](form[key], key)); 321 | } 322 | 323 | return carry; 324 | }; 325 | 326 | isString = item => typeof item === "string"; 327 | 328 | isArray = item => typeof item === "object" && Array.isArray(item); 329 | 330 | isObject = item => typeof item === "object" && !Array.isArray(item); 331 | } 332 | -------------------------------------------------------------------------------- /src/utils/factory.js: -------------------------------------------------------------------------------- 1 | export function defaultValidation(type) { 2 | switch (type) { 3 | case "required": 4 | return "field is required"; 5 | case "email": 6 | return "Invalid email address"; 7 | case "number": 8 | return "field has to be a numeric value"; 9 | case "url": 10 | return "field has to be a valid Url"; 11 | case "max": 12 | return; 13 | default: 14 | return "validation failed"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import Validate from "./Validate"; 2 | import Error from "./Error"; 3 | 4 | export default class Form { 5 | constructor(data, validationRules) { 6 | this.originalData = data; 7 | this.validationRules = validationRules; 8 | 9 | for (let field in data) { 10 | this[field] = data[field]; 11 | } 12 | 13 | this.validator = new Validate(data); 14 | this.errors = new Error(); 15 | } 16 | 17 | get(key) { 18 | return this[key]; 19 | } 20 | 21 | set(data, value) { 22 | this[data] = value; 23 | const formData = this; 24 | let validation = this.validator.validate(formData, this.validationRules); 25 | 26 | if (validation.validated) { 27 | this.errors.clear(data); 28 | } else { 29 | this.errors.record(validation.error); 30 | } 31 | 32 | return this; 33 | } 34 | 35 | validated() { 36 | return this.validator.valid; 37 | } 38 | 39 | setError(errors) { 40 | this.errors.record(errors); 41 | 42 | return this; 43 | } 44 | 45 | validate() { 46 | const formData = this; 47 | 48 | let validation = this.validator.validate(formData, this.validationRules); 49 | 50 | return { 51 | validated: validation.validated, 52 | errors: validation.error 53 | }; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/utils/integration/form.test.js: -------------------------------------------------------------------------------- 1 | import Form from "./../index"; 2 | 3 | describe("Validation with different type of rules being called", () => { 4 | it("can validate data with rules using string", () => { 5 | const rules = { 6 | email: "required|email", 7 | password: "required|between:5:10" 8 | }; 9 | 10 | const data = { 11 | email: "semmivp1@gmail.com", 12 | password: "secret" 13 | }; 14 | 15 | const form = new Form(data, rules); 16 | 17 | const validation = form.validate(); 18 | 19 | expect(validation.validated).toBeTruthy(); 20 | expect(validation.errors).toBeUndefined(); 21 | }); 22 | 23 | it("can validate data with rules using array", () => { 24 | const rules = { 25 | email: ["required", "email"], 26 | password: ["required", "between:5:10"] 27 | }; 28 | 29 | const data = { email: "semmivp1@gmail.com", password: "secret" }; 30 | 31 | const form = new Form(data, rules); 32 | 33 | const validation = form.validate(); 34 | 35 | expect(validation.validated).toBeTruthy(); 36 | expect(validation.errors).toBeUndefined(); 37 | }); 38 | 39 | it("can validate data with rules using object", () => { 40 | const rules = { 41 | email: { 42 | rules: ["required", "email"] 43 | } 44 | }; 45 | 46 | const data = { 47 | email: "semmivp1@gmail.com" 48 | }; 49 | 50 | const form = new Form(data, rules); 51 | 52 | const validation = form.validate(); 53 | expect(validation.validated).toBeTruthy(); 54 | expect(validation.errors).toBeUndefined(); 55 | }); 56 | }); 57 | 58 | describe("Validation will show error message when failed the validation rules", () => { 59 | it("should show default message when failed the validation rules", () => { 60 | const rules = { 61 | email: "required|email", 62 | password: "required|between:5:10" 63 | }; 64 | 65 | const data = { 66 | email: "", 67 | password: "" 68 | }; 69 | 70 | const form = new Form(data, rules); 71 | 72 | const validaton = form.validate(); 73 | 74 | expect(validaton.validated).toBeFalsy(); 75 | 76 | expect(validaton.errors.email).toBe( 77 | "email field is required, email field is invalid email address" 78 | ); 79 | expect(validaton.errors.password).toBe( 80 | "password field is required, password field must be at least 5 character and not exceed 10 characters" 81 | ); 82 | }); 83 | 84 | it("should show global custom message if provided using object rules validation", () => { 85 | const rules = { 86 | email: { 87 | rules: ["required", "email"], 88 | message: "Please allow me to filling your inbox" 89 | } 90 | }; 91 | 92 | const data = { email: "" }; 93 | 94 | const form = new Form(data, rules); 95 | 96 | const validation = form.validate(); 97 | 98 | expect(validation.validated).toBeFalsy(); 99 | expect(validation.errors.email).toBe( 100 | "Please allow me to filling your inbox" 101 | ); 102 | }); 103 | 104 | it("should show custom message for each provided key in the message object", () => { 105 | const rules = { 106 | email: { 107 | rules: ["required", "email"], 108 | message: { 109 | required: "You shall pass if you fill this", 110 | email: "Allow me to spam your mail" 111 | } 112 | } 113 | }; 114 | 115 | const data = { email: "" }; 116 | 117 | const form = new Form(data, rules); 118 | 119 | const validation = form.validate(); 120 | 121 | expect(validation.validated).toBeFalsy(); 122 | 123 | expect(validation.errors.email).toBe( 124 | "You shall pass if you fill this, Allow me to spam your mail" 125 | ); 126 | }); 127 | 128 | it("can mix custom message and default message if the key for failed validation not exist", () => { 129 | const rules = { 130 | email: { 131 | rules: ["required", "email"], 132 | message: { 133 | required: "You shall pass if you fill this" 134 | } 135 | } 136 | }; 137 | 138 | const data = { email: "" }; 139 | 140 | const form = new Form(data, rules); 141 | 142 | const validation = form.validate(); 143 | 144 | expect(validation.validated).toBeFalsy(); 145 | 146 | expect(validation.errors.email).toBe( 147 | "You shall pass if you fill this, email field is invalid email address" 148 | ); 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /src/utils/unit/validation.test.js: -------------------------------------------------------------------------------- 1 | import Validate from "./../Validate"; 2 | 3 | function makeValidate(data = {}) { 4 | const validationClass = new Validate(data); 5 | validationClass.rawForm = { 6 | ...data, 7 | validationRules: data 8 | }; 9 | 10 | return validationClass; 11 | } 12 | 13 | describe("Our Validation rules is working as we expected ", () => { 14 | it("required should fail with empty object, string, array and null", () => { 15 | const validation = makeValidate({ email: {} }); 16 | 17 | const requiredObject = validation.required({}, "email"); 18 | const requiredArray = validation.required([], "email"); 19 | const requiredString = validation.required("", "email"); 20 | const requiredNull = validation.required(null, "email"); 21 | const requiredUndefined = validation.required(undefined, "email"); 22 | 23 | expect(requiredObject.error).toBeTruthy(); 24 | expect(requiredArray.error).toBeTruthy(); 25 | expect(requiredString.error).toBeTruthy(); 26 | expect(requiredNull.error).toBeTruthy(); 27 | expect(requiredUndefined.error).toBeTruthy(); 28 | }); 29 | 30 | it("email should fail with invalid email", () => { 31 | const validation = makeValidate({ email: {} }); 32 | 33 | const email1 = validation.email("semmivp1@gmail.com", "email"); 34 | const email2 = validation.email("verian_semmi@yahoo.co.id", "email"); 35 | const emailFailed = validation.email("semmi verian", "email"); 36 | 37 | expect(email1.error).toBeFalsy(); 38 | expect(email2.error).toBeFalsy(); 39 | expect(emailFailed.error).toBeTruthy(); 40 | }); 41 | 42 | it("number should fail with NaN number given", () => { 43 | const validation = makeValidate({ number: {} }); 44 | 45 | const number = validation.number(100, "number"); 46 | const float = validation.number(10.12, "number"); 47 | const string = validation.number("semmi verian", "number"); 48 | 49 | expect(number.error).toBeFalsy(); 50 | expect(float.error).toBeFalsy(); 51 | expect(string.error).toBeTruthy(); 52 | }); 53 | 54 | it("url should fail with unproperly formatted url", () => { 55 | const validation = makeValidate({ url: {} }); 56 | 57 | const validUrl1 = validation.url("https://www.google.com", "url"); 58 | // const validUrl2 = validation.url('www.google.com', 'url') 59 | // const validUrl3 = validation.url('google.com', 'url') 60 | const invalidUrl = validation.url("google", "url"); 61 | 62 | expect(validUrl1.error).toBeFalsy(); 63 | // expect(validUrl2.error).toBeFalsy(); 64 | // expect(validUrl3.error).toBeFalsy(); 65 | expect(invalidUrl.error).toBeTruthy(); 66 | }); 67 | 68 | it("max should fail when the given string character exceed the parameter", () => { 69 | const validation = makeValidate({ max: {} }); 70 | 71 | const validMax = validation.max("semmiverian", "max", [11]); 72 | const invalidMax = validation.max("semmiverian", "max", [10]); 73 | 74 | expect(validMax.error).toBeFalsy(); 75 | expect(invalidMax.error).toBeTruthy(); 76 | }); 77 | 78 | it("min should fail when the given string character less than the parameter", () => { 79 | const validation = makeValidate({ min: {} }); 80 | 81 | const validMin = validation.min("semmiverian", "min", [1]); 82 | const invalidMin = validation.min("semmiverian", "min", [12]); 83 | 84 | expect(validMin.error).toBeFalsy(); 85 | expect(invalidMin.error).toBeTruthy(); 86 | }); 87 | 88 | it("less than should fail when number is less than the parameter", () => { 89 | const validation = makeValidate({ lessThan: {} }); 90 | 91 | const validLessThan = validation.lessThan(10, "lessThan", [20]); 92 | const invalidLessThan = validation.lessThan(10, "lessThan", [5]); 93 | 94 | expect(validLessThan.error).toBeFalsy(); 95 | expect(invalidLessThan.error).toBeTruthy(); 96 | }); 97 | 98 | it("greater than should fail when number is greater than the parameter", () => { 99 | const validation = makeValidate({ greaterThan: {} }); 100 | 101 | const validGreaterThan = validation.greaterThan(10, "greaterThan", [8]); 102 | const invalidGreaterThan = validation.greaterThan(10, "greaterThan", [11]); 103 | 104 | expect(validGreaterThan.error).toBeFalsy(); 105 | expect(invalidGreaterThan.error).toBeTruthy(); 106 | }); 107 | 108 | it("between should fail when character string is not between the parameters given", () => { 109 | const validation = makeValidate({ between: {} }); 110 | 111 | const validBetween = validation.between("semmi verian", "between", [1, 12]); 112 | const invalidBetween = validation.between("semmi verian", "between", [ 113 | 15, 114 | 20 115 | ]); 116 | 117 | expect(validBetween.error).toBeFalsy(); 118 | expect(invalidBetween.error).toBeTruthy(); 119 | }); 120 | 121 | it("date should fail when the given data is not a valid data", () => { 122 | const validation = makeValidate({ date: {} }); 123 | 124 | const validDate = validation.date("2018-06-28", "date"); 125 | const validDate2 = validation.date("2018-06-28T07:48:00", "date"); 126 | const invalidDate = validation.date("semmi verian", "date"); 127 | 128 | expect(validDate.error).toBeFalsy(); 129 | expect(validDate2.error).toBeFalsy(); 130 | expect(invalidDate.error).toBeTruthy(); 131 | }); 132 | 133 | it("if exist should fail when the data is exist and the given validation is failed", () => { 134 | const validation = makeValidate({ 135 | ifExist: {}, 136 | email: "semmivp1@gmail.com" 137 | }); 138 | 139 | const validIfExist = validation.ifExist( 140 | "data that will be passed to required", 141 | "ifExist", 142 | ["email", "required"] 143 | ); 144 | const validIfExist2 = validation.ifExist( 145 | "data that will be passed to required", 146 | "ifExist", 147 | ["non-existence-key", "required"] 148 | ); 149 | const invalidIfExist = validation.ifExist("", "ifExist", [ 150 | "email", 151 | "required" 152 | ]); 153 | 154 | expect(validIfExist.error).toBeFalsy(); 155 | expect(validIfExist2.error).toBeFalsy(); 156 | expect(invalidIfExist.error).toBeTruthy(); 157 | }); 158 | 159 | it("if doesnt exist should fail when the data is exist and the given validation is failed", () => { 160 | const validation = makeValidate({ 161 | ifDoesntExist: {}, 162 | email: "semmivp1@gmail.com" 163 | }); 164 | 165 | const validIfDoesntExist = validation.ifDoesntExist( 166 | "data that will be passed to required", 167 | "ifDoesntExist", 168 | ["email", "required"] 169 | ); 170 | const validIfDoesntExist2 = validation.ifDoesntExist( 171 | "data that will be passed to required", 172 | "ifDoesntExist", 173 | ["non-existence-key", "required"] 174 | ); 175 | const invalidIfDoesntExist = validation.ifDoesntExist("", "ifDoesntExist", [ 176 | "non-existence-key", 177 | "required" 178 | ]); 179 | 180 | expect(validIfDoesntExist.error).toBeFalsy(); 181 | expect(validIfDoesntExist2.error).toBeFalsy(); 182 | expect(invalidIfDoesntExist.error).toBeTruthy(); 183 | }); 184 | 185 | it("when true should fail if the given validation is failed and string true is available", () => { 186 | const validation = makeValidate({ whenTrue: "", required: "" }); 187 | 188 | const validWhenTrue = validation.whenTrue( 189 | "data that will be passed", 190 | "whenTrue", 191 | ["true", "required"] 192 | ); 193 | const validWhenTrue2 = validation.whenTrue( 194 | "data that will be passed", 195 | "whenTrue", 196 | ["non-true-string", "required"] 197 | ); 198 | const invalidWhenTrue = validation.whenTrue("", "whenTrue", [ 199 | "true", 200 | "required" 201 | ]); 202 | 203 | expect(validWhenTrue.error).toBeFalsy(); 204 | expect(validWhenTrue2.error).toBeFalsy(); 205 | expect(invalidWhenTrue.error).toBeTruthy(); 206 | }); 207 | 208 | it("when false should fail if the given validation is failed and string false is available", () => { 209 | const validation = makeValidate({ whenFalse: "", required: "" }); 210 | 211 | const validWhenFalse = validation.whenFalse( 212 | "data that will be passed", 213 | "whenFalse", 214 | ["false", "required"] 215 | ); 216 | const validWhenFalse2 = validation.whenFalse( 217 | "data that will be passed", 218 | "whenFalse", 219 | ["non-true-string", "required"] 220 | ); 221 | const invalidWhenFalse = validation.whenFalse("", "whenFalse", [ 222 | "false", 223 | "required" 224 | ]); 225 | 226 | expect(validWhenFalse.error).toBeFalsy(); 227 | expect(validWhenFalse2.error).toBeFalsy(); 228 | expect(invalidWhenFalse.error).toBeTruthy(); 229 | }); 230 | 231 | it("in Array shoudl fail if the given data is not exists at available array data", () => { 232 | const data = ["indonesia", "singapore", "malaysia"]; 233 | const validation = makeValidate({ inArray: "" }); 234 | 235 | const validInArray = validation.inArray("indonesia", "inArray", data); 236 | const invalidInArray = validation.inArray("brunei", "inArray", data); 237 | 238 | expect(validInArray.error).toBeFalsy(); 239 | expect(invalidInArray.error).toBeTruthy(); 240 | }); 241 | 242 | it("start with should fail if the array of parameters doesnot start with the given data", () => { 243 | const data = ["indonesia", "singapore", "malaysia"]; 244 | const validation = makeValidate({ startsWith: "" }); 245 | 246 | const validStartWiths = validation.startsWith("indo", "startsWith", data); 247 | const invalidStartWiths = validation.startsWith("brun", "startsWith", data); 248 | 249 | expect(validStartWiths.error).toBeFalsy(); 250 | expect(invalidStartWiths.error).toBeTruthy(); 251 | }); 252 | 253 | it("end with should fail if the array of parameters doesnot end with the given data", () => { 254 | const data = ["indonesia", "singapore", "malaysia"]; 255 | const validation = makeValidate({ endsWith: "" }); 256 | 257 | const validEndsWith = validation.endsWith("nesia", "endsWith", data); 258 | const invalidEndsWith = validation.endsWith("brun", "endsWith", data); 259 | 260 | expect(validEndsWith.error).toBeFalsy(); 261 | expect(invalidEndsWith.error).toBeTruthy(); 262 | }); 263 | }); 264 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: path.resolve(__dirname, "src/index.js"), 5 | output: { 6 | path: path.resolve(__dirname, "dist"), 7 | filename: "index.js", 8 | library: "", 9 | libraryTarget: "commonjs" 10 | }, 11 | mode: "production", 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js$/, 16 | exclude: /node_modules/, 17 | use: { 18 | loader: "babel-loader" 19 | } 20 | } 21 | ] 22 | }, 23 | externals: { 24 | // Don't bundle react or react-dom 25 | react: { 26 | commonjs: "react", 27 | commonjs2: "react", 28 | amd: "React", 29 | root: "React" 30 | }, 31 | "react-dom": { 32 | commonjs: "react-dom", 33 | commonjs2: "react-dom", 34 | amd: "ReactDOM", 35 | root: "ReactDOM" 36 | } 37 | } 38 | }; 39 | --------------------------------------------------------------------------------