-
Notifications
You must be signed in to change notification settings - Fork 0
Examples
There are times when two or more fields on a form are inextricably linked to one another, but generating separate validation messages could lead to confusion. For example, say you have a validation that is dependent on another field that stipulates if Back to the Future is selected from one list, that the only acceptable rating is 10/10. In this situation, a user could select a rating first, then select the movie, and the validation wouldn't trigger until after the form submission. For a small form this might be an accepted cost, but if it is a long form, it would be nice to let the user know right away that they have entered an invalid state.
Codependent Validation is easily accomplished by creating a function that stipulates which properties of the schema should execute with validateAll. See "validateTogether" below:
import React, { useState, FC } from 'react';
import { Dog } from '...';
import { DogValidation } from '...';
export const Example2: FC = () => {
const [state, setState] = useState<Dog>({
name: '',
breed: '',
});
const v = DogValidation();
const validateTogether = (name: string, data: any) => {
const properties = ['name', 'breed'];
properties.includes(name) && v.validateAll(data, properties);
};
const handleChange = (event: any) => {
{ ... }
validateTogether(name, updatedState);
};
const handleBlur = (event: any) => {
const { name } = event.target;
validateTogether(name, state);
};
const handleSubmit = (e: any) => { ... };
return (
<form onSubmit={handleSubmit}>
<div>
<label>Name</label>
<input
name="name"
onBlur={handleBlur}
onChange={handleChange}
value={state.name}
/>
{v.getError('name') && <p>{v.getError('name')}</p>}
</div>
<div>
<label>Breed</label>
<input
name="breed"
onBlur={handleBlur}
onChange={handleChange}
value={state.breed}
/>
{v.getError('breed') && <p>{v.getError('breed')}</p>}
</div>
<button>Submit</button>
</form>
);
};
Nesting is not uncommon when handling dynamic forms where a user can add as many inputs as they need. Here is a code sandbox with multiple nested forms but the basic idea is sketched out below.
import { useValidation } from 'de-formed-validations';
import { replace } from 'ramda';
import { Phone } from 'types/phone.type';
import { compose } from 'utilities/general.utils';
import {
isLength,
stringIsNotEmpty,
stringIsNumbers,
} from 'utilities/validation.utils';
// PhoneValidation :: () -> ValidationObject<Phone>
export const PhoneValidation = () => {
return useValidation<Phone>({
number: [
{
errorMessage: 'Number is required.',
validation: stringIsNotEmpty,
},
{
errorMessage: 'Can only have digits.',
validation: compose(
stringIsNumbers,
replace(/[-.() ]/g, '')
),
},
{
errorMessage: 'Must be 10 digits.',
validation: compose(
isLength(10),
replace(/[-.() ]/g, '')
),
},
],
description: [
{
errorMessage: 'Description is required.',
validation: stringIsNotEmpty,
},
],
});
};
import { useValidation } from 'de-formed-validations';
import { Pet } from 'types/pet.type';
import { isCat, isDog } from 'utilities/pet.utils';
import { matchString, stringIsNotEmpty } from 'utilities/validation.utils';
// PetValidation :: () -> ValidationObject<Pet>
export const PetValidation = () => {
return useValidation<Pet>({
name: [
{
errorMessage: 'Name is required.',
validation: stringIsNotEmpty,
},
],
breed: [
{
errorMessage: 'Breed is required.',
validation: stringIsNotEmpty,
},
{
errorMessage: 'Garfield is a Persian/Tabby',
validation: (val: string, pet: Pet) => {
if (isCat(pet)) {
return matchString(pet.name, 'Garfield')
? matchString(val, 'Persian/Tabby')
: true;
}
return true;
},
},
],
favoriteChewToy: [
{
errorMessage: 'Favorite Chew Toy is required.',
validation: (val: string, pet: Pet) => {
return isDog(pet) ? stringIsNotEmpty(val) : true;
},
},
],
sleepingHabits: [
{
errorMessage: 'Sleeping Habits is required.',
validation: (val: string, pet: Pet) => {
return isCat(pet) ? stringIsNotEmpty(val) : true;
},
},
],
});
};
import { useValidation } from 'de-formed-validations';
import { map } from 'ramda';
import { Contact } from 'types/contact.type';
import { Pet } from 'types/pet.type';
import { all, compose } from 'utilities/general.utils';
import {
containsNoNumbers,
emailIsValid,
stringIsLessThan,
stringIsNotEmpty,
} from 'utilities/validation.utils';
import { PetValidation } from './pet.validation';
import { PhoneValidation } from './phone.validation';
// ContactValidation :: () -> ValidationObject<Contact>
export const ContactValidation = () => {
const { validateAll: validatePhone } = PhoneValidation();
const { validateAll: validatePet } = PetValidation();
return useValidation<Contact>({
name: [
{
errorMessage: 'Name is required.',
validation: stringIsNotEmpty,
},
{
errorMessage: 'Name cannot contain numbers.',
validation: containsNoNumbers,
},
{
errorMessage: 'Name must be less than 40 characters.',
validation: stringIsLessThan(40),
},
],
subscriptionEmail: [
{
errorMessage: 'Please provide an email.',
validation: (val: string, contact: Contact) => {
return contact.isSubcribed ? stringIsNotEmpty(val) : true;
},
},
{
errorMessage: 'Email is invalid.',
validation: (val: string, contact: Contact) => {
return contact.isSubcribed ? emailIsValid(val) : true;
},
},
],
phones: [
{
errorMessage: 'Not all phones provided are valid.',
validation: compose(
all,
map(validatePhone)
),
},
],
dog: [
{
errorMessage: 'Must be a valid dog.',
validation: (val: Pet) => validatePet(val),
},
],
cat: [
{
errorMessage: 'Must be a valid cat.',
validation: (val: Pet) => validatePet(val),
},
],
});
};