A simple, robust and flexible react forms validation library for both React Js and React Native. The great thing about the library is, it has no dependency on the UI. You can easily decouple your business logic from the UI and can reuse in both React Js and React Native apps.
A simple, robust and flexible react forms validation library for both React Js and React Native. The great thing about the library is, it has no dependency on the UI. You can easily decouple your business logic from the UI and can reuse in both React Js and React Native apps.
npm install @enfometa/em-forms
import { useEmForms, required, email } from "@enfometa/em-forms";
const forms = useEmForms({
forms: [
{
name: "username",
value: "",
validators: [
{ name: "required", func: required, message: "Username is required" },
{ name: "email", func: email, message: "Invalid email address" },
],
},
{
name: "password",
value: "",
validators: [{ name: "required", func: required, message: "Password is required" }],
},
],
});
const login = () => {
if (forms.validate()) {
//do something
}
};
import { EmFormGroup, EmFormErrorMessage, EmFormControl } from "@enfometa/em-forms";
<EmFormGroup emForms={forms}>
<div>
<EmFormControl formName="username">
<input type="email" className="form-control" placeholder="Email" />
</EmFormControl>
<div className="error-message">
<EmFormErrorMessage formName="username" validatorName="required" />
<EmFormErrorMessage formName="username" validatorName="email" />
</div>
</div>
<div>
<EmFormControl formName="password">
<input type="password" className="form-control" placeholder="password" />
</EmFormControl>
<div className="error-message">
<EmFormErrorMessage formName="password" validatorName="required" />
</div>
</div>
<button className="w-100 btn btn-primary btn-lg" type="button" onClick={login}>
Login
</button>
</EmFormGroup>;
EmFormsCore
EmFormsCore
This is core of the em-forms. This module defines all the basic functionality for the forms validation. This object can be initialized in two ways. These both ways accepts the first argument as required forms object array.
useEmForms
hook in functional components to initilize this object. In below example forms
is an instance of the EmFormsCore
. This hook will properly initialize object and define state handler.import { useEmForms, required, email } from "@enfometa/em-forms";
const forms = useEmForms({
forms: [
{
name: "username",
value: "",
validators: [
{ name: "required", func: required, message: "Username is required" },
{ name: "email", func: email, message: "Invalid email address" },
],
},
{
name: "password",
value: "",
validators: [{ name: "required", func: required, message: "Password is required" }],
},
],
onChange: (formName, value) => {
//on change
},
});
initEmForms
function in class components to initilize this object. In below example this.forms
is an instance of the EmFormsCore
. This function will properly initialize object and define state handler.import { initEmForms, required, email } from "@enfometa/em-forms";
this.forms = initEmForms(
{
forms: [
{
name: "username",
value: "",
validators: [
{ name: "required", func: required, message: "Username is required" },
{ name: "email", func: email, message: "Invalid email address" },
],
},
{
name: "password",
value: "",
validators: [{ name: "required", func: required, message: "Password is required" }],
},
],
},
this,
"forms"
);
Note: For detials of EmFormsCore
, please see section EmFormsCore Methods
A form consists of a user-defined name, initial value (value
), validators array, optional onChange
event and optiona valueConverter function.
{
name: "username",
value: "",
validators: [
{ name: "required", func: required, message: "Username is required" },
{ name: "email", func: email, message: "Invalid email address" },
],
onChange: (value) => {
//on change
}
}
valueConverter
:This function is used to convert the value comming from input or any source before assigning to the form field. For example if you want to convert string to date:
{
name: "username",
value: "",
validators: [
{ name: "required", func: required, message: "Username is required" },
{ name: "email", func: email, message: "Invalid email address" },
],
valueConverter: (value) => {
return typeOf(value) == 'string' : new Date(value) : value;
}
}
Each form has an array of validators. So we can have multiple validators for a form as given below.
{
name: "username",
value: "",
validators: [
{ name: "required", func: required, message: "Username is required" },
{ name: "email", func: email, message: "Invalid email address" },
],
}
name
: User-defined name to identify a validator
func
: A validator function, which is called when validation is required. It is described in details in below section
message
: An error message associated with the validator
param
: An optional parameter for passing some custom information like in case of min length, we can pass length as given below
{
name: "password",
value: "",
validators: [
{ name: "required", func: required, message: "Password is required" },
{ name: "minLength", func: minLength, message: "min Length required is 6", param: { minLength: 6 } },
],
}
em-forms has provided couple of built in validators to make developers life easy. Developers can define their own custom validators as well. Each validator function returns boolean
and accept an object of type
{form: EmFormControl, emForm: EmFormsObj, param: any}
Below is the list of built in validators with usage and examples
required
:{ name: "required", func: required, message: "Password is required" }
required
also accepts an optional parameter param: {acceptsWhiteSpace: true}
to specify whether form should accept white spaces.
example:
{ name: "required", func: required, message: "Password is required", param: {acceptsWhiteSpace: true} }
number
{ name: "number", func: number, message: "Please provide a valid number" }
Validates whether the input is a valid number
pattern
{ name: "pattern", func: pattern, message: "Invalid number", param: { pattern: '^[0-9]+$'} }
The param
object has a pattern
key in which the desired regular expression is provided.
minLength
{ name: "minLength", func: minLength, message: "min Length required is 6", param: { minLength: 6 } }
maxLength
{ name: "maxLength", func: maxLength, message: "Max Length required is 6", param: { maxLength: 6 } }
The param
object has a pattern
key in which the desired regular expression is provided.
email
{ name: "email", func: email, message: "Invalid email" }
range
{ name: "age", func: range, message: "Age should be betweeb 18 and 50", param: {min: 18, max: 50} }
requiredIf
{ name: "requiredIf", func: requiredIf, message: "Password is required", param: { name: 'terms_conditions', value: true } }
requiredIf
also accepts an optional parameter param: {acceptsWhiteSpace: true}
to specify whether form should accept white spaces.
example:
{ name: "requiredIf", func: requiredIf, message: "Password is required", param: { name: 'terms_conditions', value: true, acceptsWhiteSpace: true } }
The param
has two required keys name
, name of the other form which value is equal to value
. For example this form is requird when terms_conditions
form is checked (true
)
compare
{ name: "compare", func: compare, message: "Please confirm password", param: { compareTo: 'confrimPassword' } }
The param
object has a compareTo
key in which we provide other form name which value is to be compared. For example on the sign in page re type password will use this validator.
To define a custom validator we need to define a function which returns boolean
and accepts below given parameters. These parameters are passed when validator function is executed.
formControl
: Object of this form
emForms
: Object of the whole forms configuration
param
: Custom param object
Examples:
Compare validator can be defined in this way and can be used as given in above section "Validator functions"
function compare(formControl, emForms, param) {
let isValid = true;
let formCompareValue = emForms.getFormValue(param.compareTo);
if (!(formControl.value === undefined || formControl.value === null || formControl.value === "")) {
if (formCompareValue !== formControl.value) {
isValid = false;
}
}
return isValid;
}
A Max length validator can be defined as given below.
function maxLength(formControl, emForms, param) {
let isValid = true;
if (formControl.value !== undefined && formControl.value !== "" && formControl.value !== null) {
if (param !== undefined && param.maxLength !== undefined) {
let valLength = formControl.value.toString().length;
if (valLength > param.maxLength) {
isValid = false;
}
}
}
return isValid;
}
Similarly, you can define your own custom validators
em-forms exposes a couple of important react components which can be used in both React Js and React Native.
EmFormControl
Wraps an input field on which we want to apply the forms.
Example:
<EmFormControl emForms={forms} formName="username">
<input type="email" className="form-control" placeholder="Email" />
</EmFormControl>
emForms
: Object, the form object return by useEmForms
or initEmForms
formName
: String, name of the form
bindValue
: Boolean (Default: true), it specifies whether value of the wrapped input field should be bound or not. If true auto bound from the forms, otherwise user is responsible for binding value
valuePropName
: String (Default: "value"), It is the name of the attribute of the wrapped elemen to which value will be bount. For example for input type text "value" is passed which in case of checkbox it may be "checked"
onChangePropName
: String (Default: "onChange"), this is the attribute name for the onChange
event.
valueFunc
: Function (Default (e) => { return e; })
, this function specifies how to get the value from the event argument e
on the onChange
event. For example in case of React Js, we can get the value of the form in onChange
event as e.target.value
. So for React Js we will pas this as:
<EmFormControl emForms={forms} formName="username" valueFunc={(e) => { return e.target.value; })}>
<input type="email" />
</EmFormControl>
valueConverter
: This function specifies how to convert value before assinging/passing to the input control. It accepts a value and return converted value.
Note: Don't worry folks, we are here to make your life easy. We have provided a global configuration which will be discussed later to avoid passing these above props. (Thanks to enfometa)
EmFormError
This will render the children when the form has an error
<EmFormError emForms={forms} formName="username" validatorName="required">
<span>Password is required</span>
</EmFormError>
<EmFormError emForms={forms} formName="username" validatorName="email">
<span>Password is required</span>
</EmFormError>
Children of EmFormError
should be a valid react js or react native element. You can get the error message from the forms
object as well and shown along with the other custom elements like:
<EmFormError emForms={forms} formName="username" validatorName="required">
<span>{froms.getFormErrorMessage("username", "required")}</span>
</EmFormError>
<EmFormError emForms={forms} formName="username" validatorName="email">
<span>{froms.getFormErrorMessage("username", "email")}</span>
</EmFormError>
EmFormErrorMessage
This will render a string for the error message. So you need to wrap this control in valid react js or react native control like:
<div className="error-message">
<EmFormErrorMessage emForms={forms} formName="username" validatorName="required" />
<EmFormErrorMessage emForms={forms} formName="username" validatorName="email" />
</div>
This component only renders text messsage so if you are using this in react native it should be wrapped in component lik <Text>
<Text>
<EmFormErrorMessage emForms={forms} formName="username" validatorName="required" />
<EmFormErrorMessage emForms={forms} formName="username" validatorName="email" />
</Text>
EmFormGroup
Groups EmForms
, EmFormControl
, EmFormErrorMessage
into a single froms group and pass down the emForms
prop to all childrens. So instead of specifying emForms
on each and every indivisual control, specify this prop on the EmFormGroup
as given below.
<EmFormGroup emForms={forms}>
<EmFormControl formName="username">
<input type="email" />
</EmFormControl>
<div className="error-message">
<EmFormErrorMessage formName="username" validatorName="required" />
</div>
<EmFormControl formName="password">
<input type="password" />
</EmFormControl>
<div className="error-message">
<EmFormErrorMessage formName="password" validatorName="required" />
</div>
</EmFormGroup>
enfometa em-forms
exposes a global configuration object to configure a few most commonly used functionality globally based on your application type. As em-forms
is developed in such a way that it can work with both React Js & React Native. So, we have a few simple configuration user need to apply once to avoid repeating that code on each and every control as much as possible.
This global object has the following properties and signature with default values.
valueFunc: (e: UIEvent) => void;
bindValue: boolean = true;
valuePropName: string = "value";
onChangePropName: string = "onChange";
valueConverter: (any) => any = null
These above has already been described in the section for EmFormControl
Props
The control register defines each control and its nature to the em-forms once and you can go with neat and clean code for the rest of the application.
For example, the first object in the array passed to the registerEmFormControls
function, states that for control type: "input"
, should use configuration:
valuePropName: "value",
onChangePropName: "onChange",
valueFunc: (e) => e.target.value,
Similary, for control type: "select"
valuePropName: "value",
onChangePropName: "onChange",
valueFunc: (e) => e.target.value,
For checkbox, this is a little bit different, as usually developer want to bind the value with the checked
property instead of the value. Also on the onChange
event, value is extracted from the event argument as e.target.checked
instead of e.target.value
so it can be configured as:
{
valuePropName: "checked",
onChangePropName: "onChange",
valueFunc: (e) => e.target.checked,
controls: [
{
type: "input",
props: {type: "checkbox"},
},
],
},
controls
property can have multiple controls, for example as select
and input
has the same configuration, so we can combine this as single configuration object:
{
valuePropName: "value",
onChangePropName: "onChange",
valueFunc: (e) => e.target.value,
controls: [{ type: "input" }, { type: "select" }],
},
props
further specifies an control by props, in case of checkbox we have further filtered by prop/attribute
value.
This library is made flexible to configure custom components as well. So you can use this in the same way as for built in controls and components. Below configuraiton specifies a custom component:
{
valuePropName: "selectedValue",
onChangePropName: "onChange",
valueFunc: (e) => e.target.value,
controls: [{ type: "RadioGroupComponent" }],
},
You can configure this object in App.js
or Index.js
file as given below:
For React Js
//App.js
import { emFormsGlobalConfig } from "@enfometa/em-forms";
emFormsGlobalConfig.registerEmFormControls([
{
valuePropName: "value",
onChangePropName: "onChange",
valueFunc: (e) => e.target.value,
controls: [{ type: "input" }],
},
{
valuePropName: "value",
onChangePropName: "onChange",
valueFunc: (e) => e.target.value,
controls: [{ type: "select" }],
},
{
valuePropName: "checked",
onChangePropName: "onChange",
valueFunc: (e) => e.target.checked,
controls: [
{
type: "input",
props: { type: "checkbox" },
},
],
},
{
valuePropName: "selectedValue",
onChangePropName: "onChange",
valueFunc: (e) => e.target.value,
controls: [{ type: "RadioGroupComponent" }],
valueConverter : (value) => parseInt(value)
},
]);
For React Native
import { emFormsGlobalConfig } from "@enfometa/em-forms";
//if you are using 'onChange'
emFormsGlobalConfig.emFormValueFunc = (e) => e.nativeEvent.text;
//if you are using 'onChangeText'
emFormsGlobalConfig.emFormValueFunc = (e) => e;
In short this configuration tells the library how to extract value from the onChange event handler
EmFormsCore
methodsBelow is the list of public methods of the EmFormsCore
object returned by the initializer
addForm(formObject: EmFormControl): void
isValid(): boolean
isValidForm(formName: string): boolean
isValidFormValidator(formName: string, validatorName: string): boolean
getFormErrors(formName: string): FormError[]
getFormError(formName: string, validatorName: string): FormError | null
getFormErrorMessage(formName: string, validatorName: string = null): string | null
getErrors(): FormError[]
validate(): boolean
validateForm(formName: string): boolean
resetForm(formName: string, value: any): void
reset(values?: ResetConfig[] | null, excludeForms?: ResetConfig | null): void
values
configuration if not provided, set to defaults, excludes excludeForms
. By default if values
is not provided, all the values are set to defaults when object was initialized.setFormValue(formName: string, value: any): void
setFormTouch(formName: string, touched: boolean): void
setTouch(touched: boolean): void
getForm(formName: string): EmFormControl
getFormValue(formName: string): any
getFormTouch(formName: string): boolean
toModel(): any
{
username: "enfometa",
password: "abc123"
}
toArray(): KeyValue[]
Converts em-forms to array of key value objects, for example:
[
{username : "enfometa"},
{password : "abc123"}
]
setValuesFromModel(obj: any, setDefaults: boolean = true): void;
setDefaults
parameter specifies whether these values should set to defaults or not.setValues(values: KeyValue[], setDefaults: boolean = true): void;
KeyValue
and sets to the em-froms object. setDefaults
parameter specifies whether these values should set to defaults or not.setModel(model: any, allowAddProps: boolean = false): void
allowAddProps
parameter. This parameter speceifes whether new properties shoulb be added if does not exist in the model object.Below is the declarations for the em-froms modules
interface EmFormConfigControl {
type: string;
props: any;
}
interface EmFormConfig {
valueFunc: (e: UIEvent) => void;
bindValue: boolean;
valuePropName: string;
onChangePropName: string;
controls: EmFormConfigControl[];
valueConverter: (value : any) => any
}
interface EmFormsGlobalConfig {
emFormConfig: EmFormConfig;
registerEmFormControls(configArray: EmFormConfig[]): void;
getEmFormControlsRegister(): EmFormConfig[];
setEmFormGlobalConfig(defaultConfig: EmFormConfig): void;
}
export const emFormsGlobalConfig: EmFormsGlobalConfig;
interface Validator {
name: string;
func: (form: EmFormControl, emForms: EmFormsObj, param: any) => boolean;
message: string;
param?: any;
}
interface EmFormControl {
name: string;
value: any;
validators: Validator[];
onChange?: (value: any) => void;
valueConverter: (value : any) => any
}
interface KeyValue {
name: string;
value: any;
}
interface EmFormsObj {
forms: EmFormControl[];
onChange?: (formName: string, value: any) => void;
handleStateUpdate: () => void;
config?: EmFormsConfig;
}
interface FormError {
validatorName: string;
message: string;
}
interface ResetConfig {
name: string;
value: any;
}
interface EmFormsTriggersCofig {
touch: boolean;
change: boolean;
}
interface EmFormsConfig {
errorMessageTriggers: EmFormsTriggersCofig;
}
interface EmFormGroupProps {
emForms: EmFormsObj;
}
interface EmFormProps extends EmFormGroupProps {
formName: string;
bindValue: boolean;
valuePropName: string;
onChangePropName: string;
valueFunc: () => any;
valueConverter: (value : any) => any
}
interface EmFormErrorProps extends EmFormGroupProps {
formName: string;
validatorName: string;
}
interface EmFormsCore {
new (emForms: EmFormsObj);
addForm(formControl: EmFormControl): void;
isValid(): boolean;
isValidForm(formName: string): boolean;
isValidFormValidator(formName: string, validatorName: string): boolean;
getFormErrors(formName: string): FormError[];
getFormError(formName: string, validatorName: string): FormError | null;
getFormErrorMessage(formName: string, validatorName: string): string | null;
getErrors(): FormError[];
validate(): boolean;
validateForm(formName: string): boolean;
resetForm(formName: string, value: any): void;
reset(values?: ResetConfig[] | null, excludeForms?: ResetConfig | null): void;
setFormValue(formName: string, value: any): void;
setFormTouch(formName: string, touched: boolean): void;
setTouch(touched: boolean): void;
getForm(formName: string): EmFormControl;
getFormValue(formName: string): any;
getFormTouch(formName: string): boolean;
toModel(): any;
setValuesFromModel(obj: any, setDefaults: boolean): void;
setValues(values: KeyValue[], setDefaults: boolean): void;
setModel(model: any, allowAddProps: boolean): void;
}
export function useEmForms(emForms: EmFormsObj): EmFormsCore;
export function initEmForms(emForms: EmFormsObj, component: React.Component, stateKey: string): EmFormsCore;
export function EmFormGroup(props: EmFormGroupProps): React.FC;
export function EmFormControl(props: EmFormProps): React.FC;
export function EmFormError(props: EmFormErrorProps): React.FC;
export function EmFormErrorMessage(props: EmFormErrorProps): React.FC;
export function required(formControl: EmFormControl, emForms: EmFormsObj, param: any): boolean;
export function maxLength(formControl: EmFormControl, emForms: EmFormsObj, param: any): boolean;
export function minLength(formControl: EmFormControl, emForms: EmFormsObj, param: any): boolean;
export function pattern(formControl: EmFormControl, emForms: EmFormsObj, param: any): boolean;
export function email(formControl: EmFormControl, emForms: EmFormsObj, param: any): boolean;
export function requiredIf(formControl: EmFormControl, emForms: EmFormsObj, param: any): boolean;
export function compare(formControl: EmFormControl, emForms: EmFormsObj, param: any): boolean;
export function range(formControl: EmFormControl, emForms: EmFormsObj, param: any): boolean;
export function number(formControl: EmFormControl, emForms: EmFormsObj, param: any): boolean;