20
20







+



Challenges faced with form
- Maintaining state of form
- Field normalization (8234-2039-1022-3432)
- Developing Dynamic forms
- Component Reusability
- Complex form validation logic
- Code readability, refactoring, maintenance, debugging
- Redundant use of event handlers such onClick(), onChange(), onBlur(), etc.

Redux Form

class Form extends Component {
constructor(props) {
super(props);
this.state = {
// local state of your form
}
}
render() {
return (
// render your form elements here
)
}
}import { Field } from 'redux-form';
<Field
name="firstName"
label="First Name"
placeholder="John"
component={TextInput}
testingId="firstNameError"
type="string"
/>const MyOwnComponent = ({
input,
meta,
label,
placeholder,
onClick,
testingId,
}) => {
return (
<FormGroup>
<Label>
{label}
</Label>
{
meta.asyncValidating &&
<Spinner customClass="async-spinner" solid />
}
<Input
placeholder={placeholder}
valid={meta.valid}
invalid={meta.error && meta.touched}
onClick={onClick}
{...input}
/>
<FormFeedback type="invalid" data-testid={testingId}>
{meta.error}
</FormFeedback>
</FormGroup>
)
}// meta prop is passed down by redux form
// to the component, which has information
// about state of the field that redux-form
// is tracking for you
{
"visited": true,
"touched": true,
"active": false,
"asyncValidating": false,
"autofilled": false,
"dirty": true,
"error": "This email is already registered",
"form": "Form",
"invalid": true,
"pristine": false,
"submitting": false,
"submitFailed": false,
"valid": false
}// instead of exporting component directly
// we export it after wrapping it with
// HOC provided by Redux Form
const ReduxFormWithValidateJs = reduxForm({
form: 'Redux Form With ValidateJs',
destroyOnUnmount: false,
validate: validationFunction,
asyncValidate: asyncValidationFunction,
asyncBlurFields: ['email','userName'],
})(Form);
export default ReduxFormWithValidateJs;
Challenges solved by Redux Form
- Maintaining state of form
- Component Reusability
- Redundant use of event handlers such
as onClick(), onChange(), onBlur(), etc.

const validatedFormFieldObject = (value, isValid, errorMessage, touched = true) => {
return {
value,
touched,
isValid,
errorMessage,
}
}
export const isEmpty = (fieldName, fieldValue) => {
let formattedFieldValue = fieldValue;
if (formattedFieldValue.length === 0) {
return validatedFormFieldObject(formattedFieldValue, false, `${fieldName} cannot be empty`);
}
if (fieldName === 'First name' || fieldName === 'Last name') {
formattedFieldValue = fieldValue[0].toUpperCase() + fieldValue.slice(1);
}
return validatedFormFieldObject(formattedFieldValue, true, '');
}
const isAlphabetic = (value) => {
const regexp = /^[A-Za-z ]+$/;
return regexp.test(value);
}
export const isFirstNameValid = (firstName) => {
const errorObject = isEmpty('First name', firstName);
if (errorObject.isValid) {
if (!isAlphabetic(firstName)) {
return validatedFormFieldObject(firstName[0].toUpperCase() + firstName.slice(1), false, 'Numbers 0-9 and special characters such as _ ! @ # % ^ are not allowed');
}
}
return errorObject;
}
export const isLastNameValid = (lastName, firstName) => {
const errorObject = isEmpty('Last name', lastName);
if (errorObject.isValid) {
if (!isAlphabetic(lastName)) {
return validatedFormFieldObject(lastName[0].toUpperCase() + lastName.slice(1), false, 'Numbers 0-9 and special characters such as _ ! @ # % ^ not allowed');
}
if (firstName.toLowerCase() === lastName.toLowerCase()) {
return validatedFormFieldObject(lastName[0].toUpperCase() + lastName.slice(1), false, 'Last name cannot be same as first name');
}
}
return errorObject;
}
export const isEmailValid = (email) => {
const errorObject = isEmpty('Email', email);
if (errorObject.isValid) {
const regexp = /\S+@\S+\.\S+/;
if (!regexp.test(email)) {
return validatedFormFieldObject(email, false, 'Invalid email address');
}
}
return errorObject;
}
export const isPhoneNumberValid = (phoneNumber) => {
const errorObject = isEmpty('Phone Number', phoneNumber);
if (errorObject.isValid) {
if (phoneNumber.length !== 10) {
return validatedFormFieldObject(phoneNumber, false, 'Phone number too short')
}
}
return errorObject;
}
export const updateFormField = (value, state, fieldName) => {
if (fieldName === 'phoneNumber') {
if ((Number(value) && value.length <= 10 && value[0] !== ' ' && value[value.length-1] !== ' ') || value === '');
else {
return state;
}
}
return validatedFormFieldObject(value, state.isValid, '', false);
}

const validationFunction = values => {
const schema = {
firstName: {
presence: {
message: '^Required',
},
format: {
pattern: '[A-Za-z]+',
flags: 'i',
message: 'cannot have numbers 0-9 and
special characters such as _ ! @ # % ^',
},
},
lastName: {
presence: {
message: '^Required',
},
format: {
pattern: '[A-Za-z]+',
flags: 'i',
message: 'cannot have numbers 0-9 and
special characters such as _ ! @ # % ^',
},
equality: {
attribute: 'firstName',
message: 'cannot be same as first name',
comparator(lastName, firstName) {
if (firstName) {
return lastName.toLowerCase() !==
firstName.toLowerCase();
}
return false;
},
},
},const validatedFormFieldObject = (value, isValid, errorMessage, touched = true) => {
return {
value,
touched,
isValid,
errorMessage,
}
}
export const isEmpty = (fieldName, fieldValue) => {
let formattedFieldValue = fieldValue;
if (formattedFieldValue.length === 0) {
return validatedFormFieldObject(formattedFieldValue, false, `${fieldName} cannot be empty`);
}
if (fieldName === 'First name' || fieldName === 'Last name') {
formattedFieldValue = fieldValue[0].toUpperCase() + fieldValue.slice(1);
}
return validatedFormFieldObject(formattedFieldValue, true, '');
}
const isAlphabetic = (value) => {
const regexp = /^[A-Za-z ]+$/;
return regexp.test(value);
}
export const isFirstNameValid = (firstName) => {
const errorObject = isEmpty('First name', firstName);
if (errorObject.isValid) {
if (!isAlphabetic(firstName)) {
return validatedFormFieldObject(firstName[0].toUpperCase() + firstName.slice(1), false, 'Numbers 0-9 and special characters such as _ ! @ # % ^ are not allowed');
}
}
return errorObject;
}
export const isLastNameValid = (lastName, firstName) => {
const errorObject = isEmpty('Last name', lastName);
if (errorObject.isValid) {
if (!isAlphabetic(lastName)) {
return validatedFormFieldObject(lastName[0].toUpperCase() + lastName.slice(1), false, 'Numbers 0-9 and special characters such as _ ! @ # % ^ not allowed');
}
if (firstName.toLowerCase() === lastName.toLowerCase()) {
return validatedFormFieldObject(lastName[0].toUpperCase() + lastName.slice(1), false, 'Last name cannot be same as first name');
}
}
return errorObject;
}
export const isEmailValid = (email) => {
const errorObject = isEmpty('Email', email);
if (errorObject.isValid) {
const regexp = /\S+@\S+\.\S+/;
if (!regexp.test(email)) {
return validatedFormFieldObject(email, false, 'Invalid email address');
}
}
return errorObject;
}
export const isPhoneNumberValid = (phoneNumber) => {
const errorObject = isEmpty('Phone Number', phoneNumber);
if (errorObject.isValid) {
if (phoneNumber.length !== 10) {
return validatedFormFieldObject(phoneNumber, false, 'Phone number too short')
}
}
return errorObject;
}
export const updateFormField = (value, state, fieldName) => {
if (fieldName === 'phoneNumber') {
if ((Number(value) && value.length <= 10 && value[0] !== ' ' && value[value.length-1] !== ' ') || value === '');
else {
return state;
}
}
return validatedFormFieldObject(value, state.isValid, '', false);
}
email: {
presence: {
message: '^Required',
},
email: {
message: '^Invalid format',
},
},
phoneNumber: {
presence: {
message: '^Required',
},
length: {
is: 11,
message: 'too short',
},
},
}
const validationErrors = validate(values, schema);
if (validationErrors) {
return mapFormErrors(validationErrors);
}
return {};
}// instead of exporting component directly
// we export it after wrapping it with
// HOC provided by Redux Form
const ReduxFormWithValidateJs = reduxForm({
form: 'Redux Form With ValidateJs',
destroyOnUnmount: false,
validate: validationFunction,
asyncValidate: asyncValidationFunction,
asyncBlurFields: ['email','userName'],
})(Form);
export default ReduxFormWithValidateJs;
Challenges solved by Validate JS
- Handling Complex form validation logic
- Code has become more readable now, making it quite easy to maintain.
- Refactoring and debugging will be easy as the code is now divided into segments.
Handling Complex Forms

const validateForm = values => {
const schema = {
firstName: {
presence: {
message: '^Required',
},
format: {
pattern: '[A-Za-z]+',
flags: 'i',
message: 'cannot have numbers 0-9 and special
characters such as _ ! @ # % ^',
},
},
lastName: {
presence: {
message: '^Required',
},
format: {
pattern: '[A-Za-z]+',
flags: 'i',
message: 'cannot have numbers 0-9 and special
characters such as _ ! @ # % ^',
},
equality: {
attribute: 'firstName',
message: 'cannot be same as first name',
comparator(lastName, firstName) {
if (firstName) {
return lastName.toLowerCase() !== firstName.toLowerCase();
}
return false;
},
},
},
dateOfBirth: {
presence: {
message: '^Required',
},
length: {
is: 10,
message: '^Invalid date',
},
},
gender: {
presence: {
message: '^Required',
},
},
email: {
presence: {
message: '^Required',
},
email: {
message: '^Invalid format',
},
},
confirmEmail: {
presence: {
message: '^Required',
},
email: {
message: '^Invalid format',
},
equality: {
attribute: 'email',
message: '^Email doesn\'t match',
},
},
phoneNumber: {
presence: {
message: '^Required',
},
length: {
is: 11,
message: 'too short',
},
},
alternatePhoneNumber: {
length: {
is: 11,
message: 'too short',
},
equality: {
attribute: 'phoneNumber',
message: '^Cannot be same as primary phone number',
comparator(alternatePhone, primaryPhone) {
return alternatePhone !== primaryPhone;
},
},
},
password: {
presence: {
message: '^Required',
},
length: {
minimum: 6,
message: '^Minimum length should 6',
},
},
confirmPassword: {
presence: {
message: '^Required',
},
equality: {
attribute: 'password',
message: '^Password doesn\'t match',
},
},
address: {
presence: {
message: '^Required',
},
},
city: {
presence: {
message: '^Required',
},
},
state: {
presence: {
message: '^Required',
},
},
zipCode: {
presence: {
message: '^Required',
},
length: {
is: 7,
message: 'too short',
},
},
}
const validationErrors = validate(values, schema);
if (validationErrors) {
return mapFormErrors(validationErrors);
}
return {};
}
export default validateForm;
alternatePhoneNumber: {
length: {
is: 11,
message: 'too short',
},
equality: {
attribute: 'phoneNumber',
message: '^Cannot be same as primary phone number',
comparator(alternatePhone, primaryPhone) {
return alternatePhone !== primaryPhone;
},
},
},
password: {
presence: {
message: '^Required',
},
length: {
minimum: 6,
message: '^Minimum length should 6',
},
},
confirmPassword: {
presence: {
message: '^Required',
},
equality: {
attribute: 'password',
message: '^Password doesn\'t match',
},
},
address: {
presence: {
message: '^Required',
},
},
city: {
presence: {
message: '^Required',
},
},
state: {
presence: {
message: '^Required',
},
},
zipCode: {
presence: {
message: '^Required',
},
length: {
is: 7,
message: 'too short',
},
},
}
const validationErrors = validate(values, schema);
if (validationErrors) {
return mapFormErrors(validationErrors);
}
return {};
}
export default validateForm;
{
"email": "john@appleseed.com",
"firstName": "John",
"lastName": "Appleseed",
"dateOfBirth": "26/09/1974",
"gender": "male",
"confirmEmail": "john@appleseed.com",
"city": "Bengaluru",
"state": "Karnataka",
"zipCode": "123-456",
"phoneNumber": "99999-99999",
"password": "password",
"confirmPassword": "password",
"address": "#123 Block A",
"alternatePhoneNumber": "88888-88888",
}import validate from 'validate.js';
const validationFunction = values => {
const schema = {
firstName: {
presence: {
message: '^Required',
},
format: {
pattern: '[A-Za-z]+',
message: 'cannot have numbers 0-9 and special characters',
},
equality: {
attribute: 'lastName',
message: 'cannot be same as last name',
},
},
confirmEmail: {
presence: {
message: '^Required',
},
email: {
message: '^Invalid format',
},
equality: {
attribute: 'alternateEmail',
message: '^Email doesn\'t match',
comparator(alternateEmail, email) {
if (alternateEmail) {
return alternateEmail.toLowerCase()
!== email.toLowerCase();
}
return false;
},
},
},
password: {
presence: {
message: '^Required',
},
length: {
minimum: 6,
// is: 6,
message: '^Minimum length should 6',
},
},
}
const validationErrors = validate(values, schema);
if (validationErrors) {
return mappedFormErrors(validationErrors);
}
return {};
}Validators Provided By Validate JS
- Date(Format), Datetime (Too Early, Too Late), Email (Format).
- Length (Invalid, Too Short, Too Long, Wrong).
- Equality, Format, Exclusion/Inclusion.
- Type (Array, Integer, Number, String, Date, Boolean).
- URL (Message, Schemes, Allow Local, Allow Data Url).
0
Advanced issue found▲

{
"firstName": "Required",
"lastName": "Required",
"dateOfBirth": "Required",
"gender": "Required",
"confirmEmail": "Email doesn't match",
"alternatePhoneNumber": "Can't be same as primary number",
"password": "Minimum length should 6",
"confirmPassword": "Password doesn't match",
"address": "Required",
"city": "Required",
"state": "Required",
"zipCode": "Required"
}Form errors generated
by Validate JS
Writing Your Own
Custom Validators
Writing Your Own
Custom Validators
0
Advanced issue found▲
validate.validators.myValidator = (value) => {
// test the value against
// your condition and..
if (conditionFails) {
return 'Error message from your own validator'
}
}import validate from 'validate.js';
const validationFunction = values => {
const schema = {
firstName: {
presence: {
message: '^Required',
},
format: {
pattern: 'Regex',
message: 'Error Message',
},
myValidator: {},
},
}
const validationErrors = validate(values, schema);
if (validationErrors) {
return mappedFormErrors(validationErrors);
}
return {};
}
Async (Server-Side)
Validation

validate.validators.emailValidator = async (email) => {
return new validate.Promise(async (resolve) => {
// an API which checks if entered
// email is already registered
const isEmailRegistered = await emailApi(email);
if (isEmailRegistered)
resolve('^This email is already registered');
else
resolve();
});
};
validate.validators.phoneNumberValidator =
async (phoneNumber) => {
// follow same paradigm as asbove
// with the API for phone number
};const asyncValidationFunction = async (values) => {
const schema = {
email: {
emailValidator: !!values.email,
},
phoneNumber: {
phoneNumberValidator: !!values.phoneNumber,
},
};
const validationErrors = await validate
.async(values, schema)
.then(() => '', err => err);
if (validationErrors) {
throw mappedFormErrors(validationErrors);
}
};// instead of exporting component directly
// we export it after wrapping it with
// HOC provided by Redux Form
const ReduxFormWithValidateJs = reduxForm({
form: 'Redux Form With ValidateJs',
destroyOnUnmount: false,
validate: validationFunction,
asyncValidate: asyncValidationFunction,
asyncBlurFields: ['email','userName'],
})(Form);
export default ReduxFormWithValidateJs;
Pros of Validate JS
- Unit tested with 100% code coverage.
- No external dependencies required at all.
- As light as 5.05KB, minified and gzipped
- Provides a declarative way of validating javascript objects
- Not tightly coupled to any specific language or framework.
- Works with any ECMAScript 5.1 runtime which means it works in both the browser and in node.js.
- All modern browsers are supported (IE9+, Firefox 3+, Opera 10.5+, Safari 4+, Chrome).

Thank You Very Much! </>

vinaysharma14
Validate JS With Redux Form
By Vinay Sharma
Validate JS With Redux Form
- 555