├── .gitmodules
├── src
├── actions
│ ├── types
│ │ └── validate.types.js
│ └── validate.action.js
├── components
│ ├── labelErrorComponent.js
│ └── props.js
├── middleware
│ ├── validate-utils.js
│ ├── validate-default-messages.js
│ ├── validate-default-patterns.js
│ ├── validate-validators.js
│ └── validate.js
└── reducer
│ └── validate.reducer.js
├── index.js
├── .eslintrc
├── LICENSE
├── .gitignore
├── .npmignore
├── package.json
└── README.md
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "example/redux-form-validator-example"]
2 | path = example/redux-form-validator-example
3 | url = git://github.com/posabsolute/redux-form-validator-example
4 |
--------------------------------------------------------------------------------
/src/actions/types/validate.types.js:
--------------------------------------------------------------------------------
1 | /*
2 | * action types
3 | */
4 |
5 | export const MODEL_COMPONENT_DEFAULT_STATE = 'MODEL_COMPONENT_DEFAULT_STATE';
6 | export const MODEL_INPUT_VALIDATION = 'MODEL_INPUT_VALIDATION';
7 | export const MODEL_FORM_VALIDATION = 'MODEL_FORM_VALIDATION';
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 |
2 | import validateReducer from "./src/reducer/validate.reducer";
3 | import LabelError from "./src/components/labelErrorComponent";
4 | import validateProps from "./src/components/props";
5 | import * as validateActions from "./src/actions/validate.action";
6 | import * as validateTypes from "./src/actions/types/validate.types";
7 | import validateMiddleware from "./src/middleware/validate";
8 |
9 |
10 | export {validateReducer, LabelError, validateProps, validateActions, validateTypes, validateMiddleware}
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "mocha": true
7 | },
8 | "globals": {
9 | "If": true,
10 | "For": true,
11 | "connect": true
12 | },
13 | "rules": {
14 | "react/jsx-uses-react": 2,
15 | "react/jsx-uses-vars": 2,
16 | "react/react-in-jsx-scope": 2,
17 | "block-scoped-var": 0,
18 | "padded-blocks": 0,
19 | "no-console": 0,
20 | },
21 | "plugins": [
22 | "react"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/src/actions/validate.action.js:
--------------------------------------------------------------------------------
1 | import { VALIDATE } from '../middleware/validate';
2 | import {MODEL_COMPONENT_DEFAULT_STATE} from './types/validate.types';
3 |
4 | export function setDefaultValidatorState(component, model) {
5 | return {
6 | type: MODEL_COMPONENT_DEFAULT_STATE,
7 | component,
8 | model,
9 | };
10 | }
11 |
12 | export function validateInput(inputValue, inputName, component, model, inputObject, form) {
13 | return {
14 | [VALIDATE]: {
15 | inputName,
16 | inputValue,
17 | component,
18 | model,
19 | inputObject,
20 | form
21 | },
22 | };
23 | }
24 |
25 | export function validateForm(form, component, model) {
26 | return {
27 | [VALIDATE]: {
28 | form,
29 | component,
30 | model,
31 | },
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/labelErrorComponent.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Redux-form-validation
3 | * Error Label Component
4 | * You can use this component to display error messages in your app. Also support hidden classes
5 | */
6 | import React from 'react';
7 | import classNames from 'classnames/bind';
8 | /**
9 | * Error Label Component
10 | * return a stateless react component
11 | * @param {Object} classes - Default css classes always added to the component
12 | * @param {Object} field - Input store current values, inbluding if the input is valid
13 | * @return {React Component} Return the component.
14 | */
15 | export default ({classes, field}) => {
16 | const labelClass = classNames({
17 | [classes]: true,
18 | 'label-error': true,
19 | 'label-error--hidden': field.valid === false ? false : true,
20 | });
21 |
22 | return (
23 |
24 | { field.message }
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Cedric Dugas
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/middleware/validate-utils.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Redux-form-validation
3 | * Utils
4 | * Small functions that can be reused throughout the validation middleware
5 | */
6 |
7 | /**
8 | * isNumber() - returns a new element
9 | * based on the passed in tag name
10 | *
11 | * @param {Unknown} value
12 | * @return {Bool} if the value is a number
13 | */
14 | export function isNumber(value) {
15 | return (Object.prototype.toString.call(value) === '[object Number]' || Object.prototype.toString.call(value) === '[object String]') && !isNaN(parseFloat(value)) && isFinite(value.toString().replace(/^-/, ''));
16 | }
17 | /**
18 | * objectValueToArray() - returns a new array from form list object
19 | * used to change radioList object to an input arra
20 | *
21 | * @param {obj} html form object
22 | * @return {Array}
23 | */
24 | export function objectValueToArray(obj){
25 | return [...obj];
26 | }
27 | /**
28 | * Deferred
29 | * Create a function with an exposed promise
30 | *
31 | */
32 | export class Deferred {
33 | constructor() {
34 | this.promise = new Promise((resolve, reject)=> {
35 | this.reject = reject;
36 | this.resolve = resolve;
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
2 |
3 | *.iml
4 |
5 | ## Directory-based project format:
6 | .idea/
7 | # if you remove the above rule, at least ignore the following:
8 |
9 | # User-specific stuff:
10 | # .idea/workspace.xml
11 | # .idea/tasks.xml
12 | # .idea/dictionaries
13 |
14 | # Sensitive or high-churn files:
15 | # .idea/dataSources.ids
16 | # .idea/dataSources.xml
17 | # .idea/sqlDataSources.xml
18 | # .idea/dynamic.xml
19 | # .idea/uiDesigner.xml
20 |
21 | # Gradle:
22 | # .idea/gradle.xml
23 | # .idea/libraries
24 |
25 | # Mongo Explorer plugin:
26 | # .idea/mongoSettings.xml
27 |
28 | ## File-based project format:
29 | *.ipr
30 | *.iws
31 |
32 | ## Plugin-specific files:
33 |
34 | # IntelliJ
35 | /out/
36 |
37 | # mpeltonen/sbt-idea plugin
38 | .idea_modules/
39 | # JIRA plugin
40 | atlassian-ide-plugin.xml
41 |
42 | # Crashlytics plugin (for Android Studio and IntelliJ)
43 | com_crashlytics_export_strings.xml
44 | crashlytics.properties
45 | crashlytics-build.properties
46 | node_modules/
47 | .sass-cache/
48 | bower_components/
49 | dist/
50 | app/bower_components
51 | .DS_Store
52 | .tmp
53 | ios
54 | .yo-rc.json
55 | .jshintrc
56 | .gitattributes
57 | .editorconfig
58 | .babelrc
59 | npm-debug.log
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
2 |
3 | *.iml
4 |
5 | ## Directory-based project format:
6 | .idea/
7 | # if you remove the above rule, at least ignore the following:
8 |
9 | # User-specific stuff:
10 | # .idea/workspace.xml
11 | # .idea/tasks.xml
12 | # .idea/dictionaries
13 |
14 | # Sensitive or high-churn files:
15 | # .idea/dataSources.ids
16 | # .idea/dataSources.xml
17 | # .idea/sqlDataSources.xml
18 | # .idea/dynamic.xml
19 | # .idea/uiDesigner.xml
20 |
21 | # Gradle:
22 | # .idea/gradle.xml
23 | # .idea/libraries
24 |
25 | # Mongo Explorer plugin:
26 | # .idea/mongoSettings.xml
27 |
28 | ## File-based project format:
29 | *.ipr
30 | *.iws
31 |
32 | ## Plugin-specific files:
33 |
34 | # IntelliJ
35 | /out/
36 |
37 | # mpeltonen/sbt-idea plugin
38 | .idea_modules/
39 | # JIRA plugin
40 | atlassian-ide-plugin.xml
41 |
42 | # Crashlytics plugin (for Android Studio and IntelliJ)
43 | com_crashlytics_export_strings.xml
44 | crashlytics.properties
45 | crashlytics-build.properties
46 | node_modules/
47 | .sass-cache/
48 | bower_components/
49 | dist/
50 | app/bower_components
51 | .DS_Store
52 | .tmp
53 | ios
54 | .yo-rc.json
55 | .jshintrc
56 | .gitattributes
57 | .editorconfig
58 | .babelrc
59 | npm-debug.log
--------------------------------------------------------------------------------
/src/reducer/validate.reducer.js:
--------------------------------------------------------------------------------
1 | import {MODEL_COMPONENT_DEFAULT_STATE, MODEL_INPUT_VALIDATION, MODEL_FORM_VALIDATION} from '../actions/types/validate.types';
2 |
3 | const initialState = {};
4 |
5 | export default function validation(state = initialState, action) {
6 | let newState = Object.assign({}, state);
7 |
8 | switch (action.type) {
9 | case MODEL_INPUT_VALIDATION:
10 | let inputState = newState[action.component][action.model][action.inputName] || {};
11 | newState[action.component][action.model][action.inputName] = {
12 | ...inputState,
13 | ...action.state,
14 | };
15 | return newState;
16 |
17 |
18 | case MODEL_COMPONENT_DEFAULT_STATE:
19 | return {
20 | ...state,
21 | [action.component]: {
22 | [action.model]: {},
23 | },
24 | };
25 |
26 | case MODEL_FORM_VALIDATION:
27 | let modelState = newState[action.component][action.model];
28 | newState[action.component][action.model] = {
29 | ...modelState,
30 | ...action.state,
31 | }
32 | return newState;
33 |
34 | default:
35 | return state;
36 | }
37 | }
38 |
39 | /*
40 | validation = {
41 | login : {
42 | user: {
43 | url:{
44 | validate: true,
45 | message: "This field is required"
46 | }
47 | }
48 | }
49 | }
50 | */
51 |
--------------------------------------------------------------------------------
/src/middleware/validate-default-messages.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Redux-form-validation
3 | * Messages
4 | * Default validation messages, input error messages can be overriden in the model
5 | */
6 | export default {
7 | required: () => `This field is required`,
8 | acceptance: () => `You must be accept the terms`,
9 | min: (messages) => `Must be greater than or equal to ${messages.rule}`,
10 | max: (messages) => `Must be less than or equal to ${messages.rule}`,
11 | range: (messages) => `Must be between ${messages.rule[0]} and ${messages.rule[1]}`,
12 | length: (messages) => `Must be ${messages.rule} characters`,
13 | minLength: (messages) => `Must be at least ${messages.rule} characters`,
14 | maxLength: (messages) => `Must be at most ${messages.rule} characters`,
15 | minChecked: (messages) => `Choose at least ${messages.rule} options`,
16 | maxChecked: (messages) => `Choose a maximum of ${messages.rule} options`,
17 | rangeLength: (messages) => `Must be between ${messages.rule[0]} and ${messages.rule[1]} characters`,
18 | oneOf: (messages) => `Must be one of: ${messages.rule}`,
19 | equalTo: (messages) => `Must be the same as ${messages.rule}`,
20 | digits: () => `Must only contain digits`,
21 | number: () => `Must be a number`,
22 | email: () => `Must be a valid email`,
23 | url: () => `Must be a valid url`,
24 | inlinePattern: () => `Is invalid`,
25 | };
26 |
27 |
--------------------------------------------------------------------------------
/src/middleware/validate-default-patterns.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Redux-form-validation
3 | * Patterns
4 | * Expose often use regex validator patterns
5 | * Most of them are taken shamelessly from backbone.validation (https://github.com/thedersen/backbone.validation)
6 | */
7 | export default {
8 | // Matches any digit(s) (i.e. 0-9)
9 | digits: /^\d+$/,
10 |
11 | // Matches any number (e.g. 100.000)
12 | number: /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/,
13 |
14 | // Matches a valid email address (e.g. mail@example.com)
15 | email: /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,
16 |
17 | // Mathes any valid url (e.g. http://www.xample.com)
18 | url: /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i,
19 | };
20 |
--------------------------------------------------------------------------------
/src/middleware/validate-validators.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Redux-form-validation
3 | * Validators
4 | * Most of them are taken shamelessly from backbone.validation (https://github.com/thedersen/backbone.validation)
5 | */
6 | import {isNumber, Deferred, objectValueToArray} from './validate-utils';
7 | import defaultPatterns from './validate-default-patterns';
8 |
9 | export default {
10 | required(value, rule, list, type) {
11 | if(type === "[object RadioNodeList]"){
12 | const radioElements = objectValueToArray(list);
13 | const checkedInputs = radioElements.filter( item => item.checked === true);
14 | console.log(checkedInputs.length ? true : false)
15 | return checkedInputs.length ? true : false;
16 | }
17 | return value ? true : false;
18 | },
19 | // function validator
20 | // Call a function that must return true
21 | func(value, rule) {
22 | if (typeof rule !== 'function') {
23 | console.warn('func validation rule is not a function', rule);
24 | return false;
25 | }
26 | return rule(value);
27 | },
28 | // Async validator
29 | // Send a promess to the model async validation option,
30 | // the promess must be resolved or rejected by the async function
31 | async(value, rule) {
32 | const asyncPromise = new Deferred();
33 | if (typeof rule.then === 'function') {
34 | console.warn('async is not a promise', rule);
35 | asyncPromise.reject();
36 | }
37 | rule.call(asyncPromise, value);
38 | return asyncPromise;
39 | },
40 | // Acceptance validator
41 | // Validates that something has to be accepted, e.g. terms of use
42 | // true or 'true' are valid
43 | acceptance(value) {
44 | if (value != true) {
45 | return false;
46 | }
47 | return true;
48 | },
49 | // minChecked validator
50 | // Validate that the checkbox group has a minimum of box checked
51 | // the min value specified
52 | minChecked(value, rule, list) {
53 | const checkboxElements = objectValueToArray(list);
54 | const checkedInputs = checkboxElements.filter( item => item.checked === true);
55 | if (checkedInputs.length < rule) {
56 | return false;
57 | }
58 | return true;
59 | },
60 | // maxChecked validator
61 | // Validate that the checkbox group has no more than the maximum allowed of box checked
62 | // the max value specified
63 | maxChecked(value, rule, list) {
64 | const checkboxElements = objectValueToArray(list);
65 | const checkedInputs = checkboxElements.filter( item => item.checked === true);
66 | if (checkedInputs.length > rule) {
67 | return false;
68 | }
69 | return true;
70 | },
71 | // Max validator
72 | // Validates that the value has to be a number and equal to or less than
73 | // the max value specified
74 | max(value, rule) {
75 | if (!isNumber(value) || value > rule) {
76 | return false;
77 | }
78 | return true;
79 | },
80 | // Range validator
81 | // Validates that the value has to be a number and equal to or between
82 | // the two numbers specified
83 | range(value, rule) {
84 | if (!isNumber(value) || value < rule[0] || value > rule[1]) {
85 | return false;
86 | }
87 | return true;
88 | },
89 | // Length validator
90 | // Validates that the value has to be a string with length equal to
91 | // the length value specified
92 | length(value, rule) {
93 | if (typeof value !== 'string' || value.length !== rule) {
94 | return false;
95 | }
96 | return true;
97 | },
98 | // Min length validator
99 | // Validates that the value has to be a string with length equal to or greater than
100 | // the min length value specified
101 | minLength(value, rule) {
102 | if (typeof value !== 'string' || value.length < rule) {
103 | return false;
104 | }
105 | return true;
106 | },
107 | // Max length validator
108 | // Validates that the value has to be a string with length equal to or less than
109 | // the max length value specified
110 | maxLength(value, rule) {
111 | if (typeof value !== 'string' || value.length > rule) {
112 | return false;
113 | }
114 | return true;
115 | },
116 | // Range length validator
117 | // Validates that the value has to be a string and equal to or between
118 | // the two numbers specified
119 | rangeLength(value, rule) {
120 | if (typeof value !== 'string' || value.length < rule[0] || value.length > rule[1]) {
121 | return false;
122 | }
123 | return true;
124 | },
125 | // One of validator
126 | // Validates that the value has to be equal to one of the elements in
127 | // the specified array. Case sensitive matching
128 | oneOf(value, rule) {
129 | const elem = rule.find( (arrayVal) => arrayVal == value);
130 | if (!elem) {
131 | return false;
132 | }
133 | return true;
134 | },
135 |
136 | // Equal to input in form
137 | // Validates that the value has to be equal to the value of the attribute
138 | // with the name specified
139 | equalTo(value, rule, list, inputType, form) {
140 | if (value !== form[rule].value) {
141 | return false;
142 | }
143 | return true;
144 | },
145 | // Pattern validator
146 | // Validates that the value has to match the pattern specified.
147 | // Can be a regular expression or the name of one of the built in patterns
148 | pattern(value, rule) {
149 | // Don' validate empty inputs.
150 | if (!value || value.length === 0) {
151 | return true;
152 | }
153 |
154 | if (!value.toString().match(defaultPatterns[rule] || rule)) {
155 | return false;
156 | }
157 |
158 | return true;
159 | },
160 | };
161 |
162 |
--------------------------------------------------------------------------------
/src/components/props.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Redux-form-validation
3 | * Component props
4 | * props are added to an input
5 | * must be init in componentDidMount. this.validate = validate(this, myModelToValidate);
6 | */
7 |
8 | import classNames from 'classnames/bind';
9 |
10 | const validate = (component, model, validatorName) => {
11 |
12 | function getComponentName() {
13 | return validatorName || component.constructor.name;
14 | }
15 | /**
16 | * init()
17 | * Check you have everything you need to validate form & inputs
18 | * Set default component/model state in the store
19 | */
20 | function init() {
21 | if (!model.name) {
22 | console.warn('Model has no name, validation will not work');
23 | }
24 | if (!model.data) {
25 | console.warn('Your model has no validation rules under data');
26 | }
27 | if (!getComponentName()) {
28 | console.warn('Your form name is undefined, it should be passed as 3rd argument');
29 | }
30 | // Call actions set default component/model state in the store
31 | component.props.setDefaultValidatorState(getComponentName(), model.name);
32 | }
33 | init();
34 | /**
35 | * return helpers that can be used by the component
36 | */
37 | return {
38 | /**
39 | * onBlur()
40 | * Input onBlur Event is overriden to validate input
41 | * you can easily override this function & do the validation yourself with component.validate.input(evt.target.value, evt.target.name);
42 | * @param {Object} evt - input blur react event
43 | */
44 | onBlur: (evt) => {
45 | component.validate.input(evt.target.value, evt.target.name, evt.target.form.elements[evt.target.name], evt.target.form.elements);
46 | },
47 | /**
48 | * fieldStore()
49 | * get the input store state back
50 | * @param {String} field - Name of the input
51 | * @return {Object} fieldStore - The store values for this input (including if it is valid)
52 | */
53 | fieldStore: (field) => {
54 | let fieldStore = {};
55 | const store = component.props.validationStore;
56 | if(!store){ console.warn('validation store is undefined'); }
57 |
58 | if (store && store[getComponentName()] && store[getComponentName()][model.name] && store[getComponentName()][model.name][field]) {
59 | fieldStore = store[getComponentName()][model.name][field];
60 | }
61 | return fieldStore;
62 | },
63 |
64 | /**
65 | * formStore()
66 | * get the form store state back
67 | * @return {Object} formStore - The store values for this form (including if it is valid)
68 | */
69 | formStore: () => {
70 | let formStore = {};
71 | const store = component.props.validationStore;
72 | if(!store){ console.warn('validation store is undefined'); }
73 | if (store && store[getComponentName()] && store[getComponentName()][model.name] && store[getComponentName()][model.name]) {
74 | formStore = store[getComponentName()][model.name];
75 | }
76 | return formStore;
77 | },
78 | /**
79 | * classes()
80 | * return the css classes for an input, validating agains the input storex
81 | * @param {Object} classes - Passed at the component level, classes always added to the input component
82 | * @param {String} el - Name of the input
83 | * @return {String} inputClasses - Return all the classes in 1 string for this input
84 | */
85 | classes: (classes, el) => {
86 | if (!el) {
87 | console.warn('You must pass the inputname when using validate.classes');
88 | }
89 | let inputClasses;
90 | if (typeof classes === 'object') {
91 | inputClasses = {
92 | ...classes,
93 | 'input-validation-error': !component.validate.isInputValid(el),
94 | };
95 | } else {
96 | inputClasses = {
97 | [classes]: true,
98 | 'input-validation-error': !component.validate.isInputValid(el),
99 | };
100 | }
101 | return classNames(inputClasses);
102 | },
103 | /**
104 | * isInputValid()
105 | * return if the input is valid from the value in the store
106 | * @param {String} el - Name of the input
107 | * @return {Bool} isValid - Return is the input is valid
108 | */
109 | isInputValid: (el) => {
110 | let isValid = true;
111 | if (component.validate.fieldStore(el).valid === false) {
112 | isValid = false;
113 | }
114 | return isValid;
115 | },
116 | /**
117 | * input()
118 | * Call the input validation action
119 | * @param {String} value - Value of the input
120 | * @param {String} name - Name of the input
121 | * Default:
122 | * @return {Bool} isValid - input validation state
123 | * if validation incluse an async validation rule, we return a promise
124 | * @return {Promise} inputState.promise.promise - Return a promise that will trigger a resolved or reject state depending on input validity.
125 | */
126 | input: (value, name, inputObject, form) => {
127 | return component.props.validateInput(value, name, getComponentName(), model, inputObject, form);
128 | },
129 | /**
130 | * form()
131 | * Call the input validation action
132 | * @param {String} value - Value of the input
133 | * @param {String} name - Name of the input
134 | * Default:
135 | * @return {Bool} isValid - input validation state
136 | * if validation incluse an async validation rule, we return a promise
137 | * @return {Promise} inputState.promise.promise - Return a promise that will trigger a resolved or reject state depending on input validity.
138 | */
139 | formValidate: (inputs) => {
140 | return component.props.validateForm(inputs, getComponentName(), model);
141 | },
142 | };
143 | };
144 |
145 | export default validate;
146 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redux-form-validator",
3 | "version": "2.0.5",
4 | "description": "Form validation for redux",
5 | "main": "index.js",
6 | "format" : "es6",
7 | "jspm": {
8 | "format" : "es6"
9 | },
10 | "scripts": {
11 | "test": "echo \"Error: no test specified\" && exit 1"
12 | },
13 | "keywords": [
14 | "redux",
15 | "react-component",
16 | "form",
17 | "react-component",
18 | "validation",
19 | "react"
20 | ],
21 | "author": {
22 | "name": "Cedric Dugas"
23 | },
24 | "bugs": {
25 | "url": "https://github.com/posabsolute/redux-form-validator/issues"
26 | },
27 | "homepage": "https://github.com/posabsolute/redux-form-validator#readme",
28 | "license": "MIT",
29 | "dependencies": {
30 | "classnames": "^2.2.1",
31 | "react": "^0.14.3",
32 | "react-redux": "^4.0.5",
33 | "redux": "^3.0.5"
34 | },
35 | "readme": "# Redux Form Validation\nAn es6 redux form validator middleware that help you validate inputs.\n\nDemo: [Live Example](http://posabsolute.github.io/redux-flash-notification-example/) | [Source](https://github.com/posabsolute/redux-flash-notification-example)\n\nBetter Documentation: [http://posabsolute.github.io/redux-flash-notification](http://posabsolute.github.io/redux-flash-notification)\n\n\n## Integration\n\n\n1 npm install 'redux-form-validation' --save\n\n\n2 Add the reducer to your root reducer\n\n```javascript\n\nimport {validateReducer} from 'redux-form-validation';\n\nconst rootReducer = combineReducers({\n validate: validateReducer,\n});\n\nexport default rootReducer;\n```\n\n3 Add the validate middleware\n```javascript\nimport {validateMiddleware} from 'redux-form-validation';\n\nconst createStoreWithMiddleware = compose(\n validateMiddleware,\n ),\n window.devToolsExtension ? window.devToolsExtension() : f => f\n)(createStore);\n\n```\n\n4 Connect Validate store, actions & specicify the component & model to validate. It is important this be available throughout every components that use validation, you can trickle them down throught props.\n\nIn your componentWillMount init the validation component:\n\n```javascript\nimport {validate, validateActions} from 'redux-form-validation';\n\nconst mapStateToProps = (state) => {\n return {\n validationStore: state.validation,\n };\n};\n\nconst mapDispatchToProps = (\n dispatch,\n) => {\n return {\n ...bindActionCreators(validateActions, dispatch),\n };\n};\n\n\n@connect(mapStateToProps, mapDispatchToProps)\nexport default class LoginComponent extends React.Component {\n componentWillMount() {\n this.validate = validate(this, userModel);\n }\n render() {\n return ;\n }\n}\n\n5. Add validation to your inputs, there is also a error label component for your convenience. Unfortunately you cannot use stateless component because the middleware makes use of refs.\n\n a. add {...validate} to your input\n b. add a name & ref to your input (must be the same)\n c. To get the error class, use className={validate.classes('input-line', 'url')}\n\nIt should look like:\n```javascript\n\n\n```\n\n```javascript\nimport React from 'react';\nimport LabelError from 'components/validation/labelErrorComponent';\n\nclass LoginFormComponent extends React.Component {\n render() {\n const {validate, onSubmit} = this.props;\n return (\n \n );\n }\n}\n```\n\n6. Create a model\n\nAnatomy of a model\n\n```javascript\nconst userModel = {\n name:'userModel',\n data: {\n 'url': {\n validate: {\n required: true,\n func: (value) => {\n return true;\n },\n message: 'This is a test',\n },\n },\n },\n}\n```\n\n7 Using webpack? include jsx/es6\n```javascript\n module: {\n loaders: [{\n test:[/\\.jsx$/, /\\.js$/],\n loaders: ['react-hot', 'babel?stage=0&loose[]=es6.modules'],\n include: [\n path.resolve(__dirname, \"src\"),\n path.resolve(__dirname, \"node_modules/flash-notification-react-redux\")\n ],\n }, {\n test: [/\\.scss$/, /\\.css$/],\n loader: 'css?localIdentName=[path]!postcss-loader!sass',\n }],\n },\n};\n```\n\n8 You're done.\n\n\n## Using actions\n\nYou can use validation actions to execute code depending if the form or input is valid. It's a good way to control side effects like calling an api action once the field if valid.\n\n### Validate Sync Form\n```javascript\nonSubmit: function(validateProps) {\n const inputs = this.refs;\n if (validateProps.form(form)) {\n // form is valid, redirect to nre page\n }else{\n // form is not valid\n }\n}\n```\n### Validate Async Form\nIf you validate asyncly 1 input or form, you must use a promise instead of just using a bool.\n```javascript\nonSubmit: function submit(validateProps) {\n const inputs = this.refs;\n validateProps.form(inputs).then(() =>{\n console.log('form is valid');\n }).catch(() => { \n console.log(\"form is not valid\"); \n });\n}\n```\n\n### Validate Sync input\n\n```javascript\nif(this.validate.input(value, field)){\n // input is valid\n}else{\n // input is not valid\n}\n```\n\n\n### Validate Async input\n\n```javascript\nthis.validate.input(value, field).then(() => {\n // input is valid\n})\n.catch(function(errorMessage) {\n // input is not valid\n});\n```\n\n\n## Validation model\n\n### data\n\nA Model must have a data object that describe fields to validate. Under the validate object list all the validators you want to use.\n\n### Global Validate functions\n\nThe model can also have global validation functions that are executed once all inputs are valid.\n\n#### validate(form, dispatch)\n\nUsed to do sync validations after all your inputs are valid. Must return true or false\n\n```javascript\nconst userModel = {\n name:'userModel',\n data: {\n 'url': {\n validate: {\n required: true,\n func: (value) => {\n return true;\n },\n message: 'This is a test',\n },\n },\n },\n validate: (form, dispatch) => {\n // form\n let validate = false;\n if (!form.url.value) {\n dispatch({\n type: 'GROWLER__SHOW',\n growler: {\n text: 'Please enter your url',\n type: 'growler--error',\n },\n });\n validate = false;\n }\n\n return true;\n },\n};\n```\n\n## Built-in validators\n\n### func validator\n\nLets you implement a custom function used for validation.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'username': {\n validate: {\n required: true,\n pattern: 'email',\n async: function() {\n setTimeout( () => {\n this.resolve(\"yes\");\n }, 5000);\n },\n },\n },\n```\n\n\n\n\n### async validator\n\nLets you implement a custom async function used for validation using a Promise. Must return this.resolve or this.reject. You can reject with a custom message passed as a string.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'username': {\n validate: {\n required: true,\n pattern: 'email',\n async: function() {\n setTimeout( () => {\n this.reject(\"Sorry, this username is already used.\");\n }, 5000);\n },\n },\n },\n```\n\n### required\n\nValidates if the attribute is required or not.\nThis can be specified as either a boolean value or a function that returns a boolean value.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'username': {\n validate: {\n required: true,\n pattern: 'email',\n },\n },\n },\n};\n```\n\n### acceptance\n\nValidates that something has to be accepted, e.g. terms of use. `true` or 'true' are valid.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'username': {\n validate: {\n required: true,\n acceptance: true\n }\n }\n};\n```\n\n### min\n\nValidates that the value has to be a number and equal to or more than the min value specified.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'age': {\n validate: {\n min: 1,\n }\n }\n }\n});\n```\n\n### max\n\nValidates that the value has to be a number and equal to or less than the max value specified.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'age': {\n validate: {\n max: 100,\n }\n }\n }\n};\n```\n\n### range\n\nValidates that the value has to be a number and equal to or between the two numbers specified.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'age': {\n validate: {\n range: [1, 10],\n }\n }\n }\n};\n```\n\n### length\n\nValidates that the value has to be a string with length equal to the length value specified.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'postalCode': {\n validate: {\n length: 4,\n }\n }\n }\n};\n```\n\n### minLength\n\nValidates that the value has to be a string with length equal to or greater than the min length value specified.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'password': {\n validate: {\n minLength: 8\n }\n }\n }\n};\n```\n\n\n### maxLength\n\nValidates that the value has to be a string with length equal to or less than the max length value specified.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'password': {\n validate: {\n maxLength: 100\n }\n }\n }\n};\n```\n\n### rangeLength\n\nValidates that the value has to be a string and equal to or between the two numbers specified.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'password': {\n validate: {\n rangeLength: [6, 100]\n }\n }\n }\n};\n```\n\n### oneOf\n\nValidates that the value has to be equal to one of the elements in the specified array. Case sensitive matching.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'country': {\n validate: {\n oneOf: ['Norway', 'Sweeden']\n }\n }\n }\n};\n```\n\n### equalTo\n\nValidates that the value has to be equal to the value of the attribute with the name specified.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'password': {\n validate: {\n equalTo: 'password'\n }\n }\n }\n};\n```\n\n\n### pattern\n\nValidates that the value has to match the pattern specified. Can be a regular expression or the name of one of the built in patterns.\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'email': {\n validate: {\n pattern: 'email'\n }\n }\n }\n};\n```\n\nThe built-in patterns are:\n\n* number - Matches any number (e.g. -100.000,00)\n* email - Matches a valid email address (e.g. mail@example.com)\n* url - Matches any valid url (e.g. http://www.example.com)\n* digits - Matches any digit(s) (i.e. 0-9)\n\nSpecify any regular expression you like:\n\n```js\nconst userModel = {\n name:'userModel',\n data: {\n 'email': {\n validate: {\n pattern: /^sample/\n }\n }\n }\n};\n```\n\n\n## Limitations\n\nThis component is based on the use of redux, react, es6 & es7 (decorators) and webpack for loading the css as an import module.\n",
36 | "readmeFilename": "README.md",
37 | "_id": "redux-form-validator@1.0.0",
38 | "_shasum": "c7429bb73445d9376932aea92be3d3399ebd2ff7",
39 | "_from": "redux-form-validator@>=1.0.0 <2.0.0"
40 | }
41 |
--------------------------------------------------------------------------------
/src/middleware/validate.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Redux-form-validation
3 | * Validate Middleware
4 | * Do the validation heavy-lifting
5 | */
6 | import validateFuncs from './validate-validators';
7 | import defaultMessages from './validate-default-messages';
8 | import {Deferred} from './validate-utils';
9 |
10 | export const VALIDATE = Symbol('Validate');
11 |
12 |
13 | export default store => next => action => {
14 | const validator = action[VALIDATE];
15 | // Check if the middleware is directly called in a action
16 | if (typeof validator === 'undefined') {
17 | return next(action);
18 | }
19 | // get data from the action call
20 | const { form, inputName, inputValue, model, component, inputObject } = validator;
21 | // init is done at the end
22 | /**
23 | * validateRules()
24 | * Take each validation rule from an input and validates.
25 | *
26 | * @param {Object} rules - all the validation rules defined for an input
27 | * @param {String} inputValue - input value to validate
28 | * @return {Object} input - input status added to the validation state.
29 | */
30 | function validateRules(rules, value, inputData, inputType) {
31 | // default store value for an input
32 | let input = {
33 | valid: true,
34 | rules: [],
35 | };
36 | // Take each validation rules & validate
37 | Object.keys(rules).forEach( rule => {
38 | // if validator exist
39 | if (validateFuncs[rule]) {
40 | // return validation status
41 | const validation = validateFuncs[rule](value, rules[rule], inputData, inputType, form);
42 |
43 | // Do we have an async validation with promise
44 | if (rule === 'async') {
45 | input.isPromise = true;
46 | input.promise = validation;
47 | }
48 |
49 | // if we have an error
50 | // validation can either be false
51 | // or an error message string
52 | if (validation !== true) {
53 | input.valid = false;
54 | // add the errored rule to store
55 | input.rules.push(rule);
56 | // Define Message used
57 | const messages = {
58 | rule: rules[rule],
59 | };
60 | input.message = rules.message ||
61 | (defaultMessages[rule] && defaultMessages[rule](messages)) ||
62 | (defaultMessages[rules[rule]] && defaultMessages[rules[rule]](messages)) ||
63 | validation;
64 | }
65 | }
66 | });
67 | return input;
68 | }
69 |
70 | /**
71 | * dispatchAction()
72 | * Dispatch action to store
73 | *
74 | * @param {Object} state - input state
75 | */
76 | function dispatchInputAction(state, name) {
77 | next({
78 | type: 'MODEL_INPUT_VALIDATION',
79 | model: model.name,
80 | component,
81 | inputName: name,
82 | state,
83 | });
84 | }
85 |
86 | function dispatchFormAction(formState) {
87 | next({
88 | type: 'MODEL_FORM_VALIDATION',
89 | model: model.name,
90 | component,
91 | state: formState,
92 | });
93 | }
94 |
95 | function dispatchErrorFormAction(formState, message) {
96 | formState.valid = false;
97 | if (typeof message === 'string') {
98 | formState.message = message;
99 | }
100 | dispatchFormAction(formState);
101 | }
102 | /**
103 | * validateInput()
104 | * Validate one input (multiple rules)
105 | *
106 | * Default:
107 | * @return {Bool} isValid - input validation state
108 | * if validation incluse an async validation rule, we return a promise
109 | * @return {Promise} inputState.promise.promise - Return a promise that will trigger a resolved or reject state depending on input validity.
110 | */
111 | function validateInput(name = inputName, value = inputValue, inputData = inputObject) {
112 | // get validation rules
113 | const modelData = model.data[name];
114 | const inputType = inputData.toString();
115 | // get input state
116 | if(!modelData || !modelData.validate){
117 | console.warn('undefined validation rules for ' + name);
118 | return {
119 | isValid : true,
120 | value: value,
121 | };
122 | }
123 |
124 | let inputState = validateRules(modelData.validate, value, inputData, inputType);
125 | // we save the value for the store
126 | inputState.currentValue = value;
127 |
128 | // is the input has an async validation rule;
129 | if (inputState.isPromise) {
130 | dispatchInputAction({state:'isValidating'}, name);
131 | // in case any other rules state the input is not valid, reject promise
132 | if (!inputState.valid) { inputState.promise.reject(inputState); }
133 |
134 | // if validation function resolve promise, dispatch a valid action on the input
135 | inputState.promise.promise.then(() =>{
136 | inputState.state = 'done';
137 | inputState.valid = true;
138 | inputState.message = '';
139 | dispatchInputAction(inputState, name);
140 | })
141 | // if validation fails because of another validation rule or
142 | // any other rules state the input is not valid, reject promise
143 | .catch((state) => {
144 | inputState.state = 'done';
145 | inputState.valid = false;
146 | // other rules pass back an object
147 | if (typeof state === 'object') {
148 | inputState = state;
149 | // in case the async validation rule pass down an error message when it reject it.
150 | }else if (typeof state === 'string') {
151 | inputState.message = state;
152 | }
153 | // dispatch input error status to the store
154 | dispatchInputAction(inputState, name);
155 | });
156 | // return promise so it can be used in component code. this.validateInput('username').then()
157 | return inputState.promise.promise;
158 | }
159 | // in case we are not in a promise, dispatch input state
160 | // & return input validity, so it can be used in component code. if(this.validateInput('username')){}
161 | if(inputState.valid){
162 | inputState.message = '';
163 | }
164 | dispatchInputAction(inputState, name);
165 | return {
166 | isValid : inputState.valid,
167 | value: value,
168 | };
169 | }
170 |
171 | /**
172 | * getFormValues(form.elements)
173 | * return form values
174 | *
175 | * @param {form} form.elements -html5 form elements
176 | * @return {object} formValues - all form values
177 | */
178 | function getFormValues(formElements = form) {
179 | let formValues = {};
180 |
181 | for (let i=0; i {
241 | // form.elements contain both a numeric and named object for the same input
242 | // use the named only
243 | if(!isNaN(input)){return;}
244 | const inputState = validateInput( input, form[input].value, form[input]);
245 | // if return a bool false
246 | if (!inputState.isValid && !inputState.then) {
247 | formState.valid = false;
248 | }
249 | // if return a promise we add the promise to array for later use of promise.all()
250 | if (inputState.then) {
251 | formState.isPromise = true;
252 | promises.push(inputState);
253 | }
254 | });
255 | }
256 | /**
257 | * rejectPromise()
258 | * Small utils used by handleInputsPromises() & handleFormAsyncValidation
259 | * Used to reject early when in async mode
260 | *
261 | * @return {Promise} formState.async.promise
262 | */
263 | function rejectPromise() {
264 | formState.async.reject();
265 | formState.valid = false;
266 | dispatchFormAction(formState);
267 | return formState.async.promise;
268 | }
269 | /**
270 | * handleInputsPromises()
271 | * Used to validate all input promises
272 | * Also contain workflow to finish validation
273 | *
274 | * @return {Promise} formState.async.promise || formState.asyncAll
275 | */
276 | function handleInputsPromises() {
277 | if (formState.isPromise) {
278 | // is we already know the form is not valid form sync input validation, reject
279 | // we also validate the global model sync validation
280 | // we must reject an empty promise, you cannot reject Promise.all manually
281 | if (!formState.valid || (model.validate && !model.validate(form, store.dispatch).valid)) {
282 | return rejectPromise();
283 | }
284 | // we need to return the final promise
285 | // in the case of a inputAsync & a formAsync we must add the form async
286 | // to the inputs promise array
287 | if(model.validateAsync){
288 | promises.push(handleFormAsyncValidation());
289 | }
290 | // add all promises together
291 | formState.asyncAll = Promise.all(promises);
292 | // all the promise resolve, move ahead!
293 | formState.asyncAll.then(() =>{
294 | dispatchFormAction(formState);
295 | })
296 | // if validation fails because one of the promise does not resolve
297 | .catch((message) => {
298 | dispatchErrorFormAction(formState, message);
299 | });
300 | // when in promise mode we return a promise to the component
301 | return formState.asyncAll;
302 | }
303 | }
304 | /**
305 | * handleFormAsyncValidation()
306 | * Used to validate global model async validation
307 | *
308 | * @return {Promise} model.validateAsync.promise
309 | */
310 | function handleFormAsyncValidation() {
311 | // model has an async function
312 | if (model.validateAsync) {
313 | dispatchFormAction({state:'isValidating'});
314 | // reject early of form is already not valid
315 | if (!formState.valid || (model.validate && !model.validate(form, store.dispatch))) {
316 | return rejectPromise();
317 | }
318 | // call validate async function & return the promise
319 | model.validateAsync.call(formState.async, form, store.dispatch);
320 | formState.async.promise.then(() => {
321 | formState.state = 'done';
322 | dispatchFormAction(formState);
323 | }).catch((message) => {
324 | formState.state = 'done';
325 | dispatchErrorFormAction(formState, message);
326 | });
327 | return formState.async.promise;
328 | }
329 | }
330 |
331 | }
332 | /**
333 | * init the validation middleware
334 | * Are we validating a form or an input
335 | */
336 | if (inputName) {
337 | return validateInput();
338 | }
339 | if (form) {
340 | return validateForm();
341 | }
342 | };
343 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Redux Form Validation
2 | An es6 redux form validator middleware that helps you manage inputs. This middleware is not about keeping your global form state; it's about keeping your form validation state.
3 |
4 | If you are looking for a fully controlled input approach, you should head to redux-form.
5 |
6 | About controlled inputs: [My controlled input dilemma with react & redux](http://www.position-absolute.com/?p=5264)
7 |
8 | Demo: [Live Example](http://posabsolute.github.io/redux-form-validator-example/) | [Source](https://github.com/posabsolute/redux-form-validator-example)
9 |
10 | Documentation: [http://posabsolute.github.io/redux-form-validator](http://posabsolute.github.io/redux-form-validator)
11 |
12 |
13 | ## Integration
14 |
15 |
16 | 1 npm install 'redux-form-validation' --save
17 |
18 |
19 | 2 Add the reducer to your root reducer
20 |
21 | ```javascript
22 |
23 | import {validateReducer} from 'redux-form-validation';
24 |
25 | const rootReducer = combineReducers({
26 | validate: validateReducer,
27 | });
28 |
29 | export default rootReducer;
30 | ```
31 |
32 | 3 Add the validate middleware
33 | ```javascript
34 | import {validateMiddleware} from 'redux-form-validation';
35 |
36 | const createStoreWithMiddleware = compose(
37 | validateMiddleware,
38 | ),
39 | window.devToolsExtension ? window.devToolsExtension() : f => f
40 | )(createStore);
41 |
42 | ```
43 |
44 | 4 Connect Validate store, actions & specify the component & model to validate. It is important this be available throughout every component that uses validation, you can trickle them down through props.
45 |
46 | In your componentWillMount init the validation component:
47 |
48 | ```javascript
49 | import {validate, validateActions} from 'redux-form-validation';
50 |
51 | const mapStateToProps = (state) => {
52 | return {
53 | validationStore: state.validation,
54 | };
55 | };
56 |
57 | const mapDispatchToProps = (
58 | dispatch,
59 | ) => {
60 | return {
61 | ...bindActionCreators(validateActions, dispatch),
62 | };
63 | };
64 |
65 |
66 | @connect(mapStateToProps, mapDispatchToProps)
67 | export default class LoginComponent extends React.Component {
68 | componentWillMount() {
69 | this.validate = validate(this, userModel);
70 | }
71 | render() {
72 | return ;
73 | }
74 | }
75 | ```
76 |
77 | 5. Add validation to your inputs, there is also an error label component for your convenience.
78 |
79 | a. add {...validate} to your input
80 | b. add a name to your input (the middleware use the html5 form.elements)
81 | c. To get the error class on your input, use className={validate.classes('input-line', 'url')}
82 |
83 | It should look like:
84 | ```javascript
85 |
86 |
87 | ```
88 |
89 | ```javascript
90 | import React from 'react';
91 | import LabelError from 'components/validation/labelErrorComponent';
92 |
93 | class LoginFormComponent extends React.Component {
94 | render() {
95 | const {validate, onSubmit} = this.props;
96 | return (
97 |
111 | );
112 | }
113 | }
114 | ```
115 |
116 | 6. Create a model
117 |
118 | Anatomy of a model
119 |
120 | ```javascript
121 | const userModel = {
122 | name:'userModel',
123 | data: {
124 | 'url': {
125 | validate: {
126 | required: true,
127 | func: (value) => {
128 | return true;
129 | },
130 | message: 'This is a test',
131 | },
132 | },
133 | },
134 | }
135 | ```
136 |
137 | 7 Using webpack? include jsx/es6
138 | ```javascript
139 | module: {
140 | loaders: [{
141 | test:[/\.jsx$/, /\.js$/],
142 | loaders: ['react-hot', 'babel?stage=0&loose[]=es6.modules'],
143 | include: [
144 | path.resolve(__dirname, "src"),
145 | path.resolve(__dirname, "node_modules/redux-form-validator")
146 | ],
147 | }, {
148 | test: [/\.scss$/, /\.css$/],
149 | loader: 'css?localIdentName=[path]!postcss-loader!sass',
150 | }],
151 | },
152 | };
153 | ```
154 |
155 | 8 You're done.
156 |
157 |
158 | ## Using actions
159 |
160 | You can use validation actions to execute code depending if the form or input is valid. It's a good way to control side effects like calling an api action once the field if valid.
161 |
162 | ### Validate Sync Form
163 | ```javascript
164 | onSubmit: function(validateProps) {
165 | const inputs = this.refs;
166 | if (validateProps.form(form)) {
167 | // form is valid, redirect to nre page
168 | }else{
169 | // form is not valid
170 | }
171 | }
172 | ```
173 | ### Validate Async Form
174 | If you validate asyncly 1 input or form, you must use a promise instead of just using a bool.
175 | ```javascript
176 | onSubmit: function submit(validateProps) {
177 | const inputs = this.refs;
178 | validateProps.form(inputs).then(() =>{
179 | console.log('form is valid');
180 | }).catch(() => {
181 | console.log("form is not valid");
182 | });
183 | }
184 | ```
185 |
186 | ### Validate Sync input
187 |
188 | ```javascript
189 | if(this.validate.input(value, field)){
190 | // input is valid
191 | }else{
192 | // input is not valid
193 | }
194 | ```
195 |
196 |
197 | ### Validate Async input
198 |
199 | ```javascript
200 | this.validate.input(value, field).then(() => {
201 | // input is valid
202 | })
203 | .catch(function(errorMessage) {
204 | // input is not valid
205 | });
206 | ```
207 |
208 |
209 | ## Validation model
210 |
211 | ### data
212 |
213 | A Model must have a data object that describe fields to validate. Under the validate object list all the validators you want to use.
214 |
215 | ### Global Validate functions
216 |
217 | The model can also have global validation functions that are executed once all inputs are valid.
218 |
219 | #### validate(form, dispatch)
220 |
221 | Used to do sync validations after all your inputs are valid. Must return true or false
222 |
223 | ```javascript
224 | const userModel = {
225 | name:'userModel',
226 | data: {
227 | 'url': {
228 | validate: {
229 | required: true,
230 | func: (value) => {
231 | return true;
232 | },
233 | message: 'This is a test',
234 | },
235 | },
236 | },
237 | validate: (form, dispatch) => {
238 | // form
239 | let validate = false;
240 | if (!form.url.value) {
241 | dispatch({
242 | type: 'GROWLER__SHOW',
243 | growler: {
244 | text: 'Please enter your url',
245 | type: 'growler--error',
246 | },
247 | });
248 | validate = false;
249 | }
250 |
251 | return true;
252 | },
253 | };
254 | ```
255 |
256 | ## Built-in validators
257 |
258 | ### func validator
259 |
260 | Lets you implement a custom function used for validation.
261 |
262 | ```js
263 | const userModel = {
264 | name:'userModel',
265 | data: {
266 | 'username': {
267 | validate: {
268 | required: true,
269 | pattern: 'email',
270 | async: function() {
271 | setTimeout( () => {
272 | this.resolve("yes");
273 | }, 5000);
274 | },
275 | },
276 | },
277 | ```
278 |
279 |
280 |
281 |
282 | ### async validator
283 |
284 | Lets you implement a custom async function used for validation using a Promise. Must return this.resolve or this.reject. You can reject with a custom message passed as a string.
285 |
286 | ```js
287 | const userModel = {
288 | name:'userModel',
289 | data: {
290 | 'username': {
291 | validate: {
292 | required: true,
293 | pattern: 'email',
294 | async: function() {
295 | setTimeout( () => {
296 | this.reject("Sorry, this username is already used.");
297 | }, 5000);
298 | },
299 | },
300 | },
301 | ```
302 |
303 | ### required
304 |
305 | Validates if the attribute is required or not.
306 | This can be specified as either a boolean value or a function that returns a boolean value.
307 |
308 | ```js
309 | const userModel = {
310 | name:'userModel',
311 | data: {
312 | 'username': {
313 | validate: {
314 | required: true,
315 | pattern: 'email',
316 | },
317 | },
318 | },
319 | };
320 | ```
321 |
322 | ### acceptance
323 |
324 | Validates that something has to be accepted, e.g. terms of use. `true` or 'true' are valid.
325 |
326 | ```js
327 | const userModel = {
328 | name:'userModel',
329 | data: {
330 | 'username': {
331 | validate: {
332 | required: true,
333 | acceptance: true
334 | }
335 | }
336 | };
337 | ```
338 |
339 | ### min
340 |
341 | Validates that the value has to be a number and equal to or more than the min value specified.
342 |
343 | ```js
344 | const userModel = {
345 | name:'userModel',
346 | data: {
347 | 'age': {
348 | validate: {
349 | min: 1,
350 | }
351 | }
352 | }
353 | });
354 | ```
355 |
356 | ### max
357 |
358 | Validates that the value has to be a number and equal to or less than the max value specified.
359 |
360 | ```js
361 | const userModel = {
362 | name:'userModel',
363 | data: {
364 | 'age': {
365 | validate: {
366 | max: 100,
367 | }
368 | }
369 | }
370 | };
371 | ```
372 |
373 | ### range
374 |
375 | Validates that the value has to be a number and equal to or between the two numbers specified.
376 |
377 | ```js
378 | const userModel = {
379 | name:'userModel',
380 | data: {
381 | 'age': {
382 | validate: {
383 | range: [1, 10],
384 | }
385 | }
386 | }
387 | };
388 | ```
389 |
390 | ### length
391 |
392 | Validates that the value has to be a string with length equal to the length value specified.
393 |
394 | ```js
395 | const userModel = {
396 | name:'userModel',
397 | data: {
398 | 'postalCode': {
399 | validate: {
400 | length: 4,
401 | }
402 | }
403 | }
404 | };
405 | ```
406 |
407 | ### minLength
408 |
409 | Validates that the value has to be a string with length equal to or greater than the min length value specified.
410 |
411 | ```js
412 | const userModel = {
413 | name:'userModel',
414 | data: {
415 | 'password': {
416 | validate: {
417 | minLength: 8
418 | }
419 | }
420 | }
421 | };
422 | ```
423 |
424 |
425 | ### maxLength
426 |
427 | Validates that the value has to be a string with length equal to or less than the max length value specified.
428 |
429 | ```js
430 | const userModel = {
431 | name:'userModel',
432 | data: {
433 | 'password': {
434 | validate: {
435 | maxLength: 100
436 | }
437 | }
438 | }
439 | };
440 | ```
441 |
442 | ### rangeLength
443 |
444 | Validates that the value has to be a string and equal to or between the two numbers specified.
445 |
446 | ```js
447 | const userModel = {
448 | name:'userModel',
449 | data: {
450 | 'password': {
451 | validate: {
452 | rangeLength: [6, 100]
453 | }
454 | }
455 | }
456 | };
457 | ```
458 |
459 | ### oneOf
460 |
461 | Validates that the value has to be equal to one of the elements in the specified array. Case sensitive matching.
462 |
463 | ```js
464 | const userModel = {
465 | name:'userModel',
466 | data: {
467 | 'country': {
468 | validate: {
469 | oneOf: ['Norway', 'Sweeden']
470 | }
471 | }
472 | }
473 | };
474 | ```
475 |
476 | ### equalTo
477 |
478 | Validates that the value has to be equal to the value of the attribute with the name specified.
479 |
480 | ```js
481 | const userModel = {
482 | name:'userModel',
483 | data: {
484 | 'password': {
485 | validate: {
486 | equalTo: 'password'
487 | }
488 | }
489 | }
490 | };
491 | ```
492 |
493 |
494 | ### pattern
495 |
496 | Validates that the value has to match the pattern specified. Can be a regular expression or the name of one of the built in patterns.
497 |
498 | ```js
499 | const userModel = {
500 | name:'userModel',
501 | data: {
502 | 'email': {
503 | validate: {
504 | pattern: 'email'
505 | }
506 | }
507 | }
508 | };
509 | ```
510 |
511 | The built-in patterns are:
512 |
513 | * number - Matches any number (e.g. -100.000,00)
514 | * email - Matches a valid email address (e.g. mail@example.com)
515 | * url - Matches any valid url (e.g. http://www.example.com)
516 | * digits - Matches any digit(s) (i.e. 0-9)
517 |
518 | Specify any regular expression you like:
519 |
520 | ```js
521 | const userModel = {
522 | name:'userModel',
523 | data: {
524 | 'email': {
525 | validate: {
526 | pattern: /^sample/
527 | }
528 | }
529 | }
530 | };
531 | ```
532 |
533 | ## Contributions
534 |
535 | There is plenty to do in the issue tracker, look at the 1.1 milestone
536 |
537 |
538 | ## Limitations
539 |
540 | This component is based on the use of redux, react, es6 & es7 (decorators) and webpack for loading the css as an import module.
541 |
--------------------------------------------------------------------------------