Skip to content

perscrew/react-simple-form-validator

Repository files navigation

React simple form validator

Node.js CI Npm package version Maintenance TypeScript

React simple form validator is a simple library to validate your form fields with React JS or React native. The library is easy to use and is written with typescript.

The library exposes a useValidation hook or a ValidationComponent for class based component.

You can view a demo HERE.

1. Installation

  • Run the following commands :
# with npm
npm install react-simple-form-validator --save

# or with yarn
yarn add react-simple-form-validator

2. Use it in your app

For Functional component:

You will use useValidation hook inside your component like this :

import { useValidation } from 'react-simple-form-validator';

const MyFunction = () => {
  const [email, setEmail] = useState('');
  const [name, setName] = useState('');
  
  const { isFieldInError, getErrorsInField, isFormValid } = useValidation({
    fieldsRules: {
      email: { email: true },
      name: { required: true }
    },
    state: { email, name }
  });
  
  return (
    <form>
      <input
            id="email"
            type="text"
            onChange={(e) => setEmail(e.target.value)}
            value={email}
          />
      <input
            id="name"
            type="text"
            onChange={(e) => setName(e.target.value)}
            value={name}
          />
    </form>
  );
}

You need to pass the form field state to the useValidation hook in the state propery like above.

You can also pass custom messages, labels, rules, locale (check the documentation below).

The description of the object returned by the hook is :

Function / Variable Output Benefits
isFormValid boolean This variable indicates if the form is valid and if there are no errors.
isFieldInError(fieldName: string) boolean This function indicates if a specific field has an error. The field name will match with your form state
getErrorMessages(separator: string, separator = '\n') string This method returns the different error messages bound to your form state. The argument is optional, by default the separator is a \n. Under the hood a join method is used.
getErrorsInField(fieldName: string) string[] This method returns the error messages bound to the specified field. The field name will match with your form state. It returns an empty array if no error was bound to the field.
getErrorsForField(fieldName: string) Errors This method returns an Errors object containing 3 properties : fieldName: string, failedRules: string[], and messages: string[].
getFailedRules() {[fieldName: string]: string[]} This methods returns the failed rules of your form state
getFailedRulesInField(fieldName: string) string[] This method returns the failed rules bound to a specific field name.

For class component:

Extend "ValidationComponent" class on a your component :

import React from 'react';
import ValidationComponent from 'react-simple-form-validator';

export default class MyForm extends ValidationComponent {
  ...
}

The ValidationComponent extends the React.Component class.

To ensure form validation you have to call the validate method in a custom component (herited from ValidationComponent)

class ClassForm extends ValidationComponent {
  constructor(props) {
    super(props);

    this.fieldRules = {
      email: { email: true },
      name: { required: true }
    };

    this.state = {
      email: '',
      name: ''
    };
  }

   render() {
    return ( 
        <form>
            <input
              id="email"
              type="email"
              onChange={(e) => this.validate({ email: e.target.value, fieldRules: this.fieldRules })}
              value={this.state.email}
              placeholder="Seize an email"
            />
            <input
              id="name"
              type="text"
              onChange={(e) => this.validate({ name: e.target.value, fieldRules: this.fieldRules })}
              value={this.state.name}
              placeholder="Seize a name"
            />
          <button disabled={!this.isFormValid}>Submit</button>
        </form>
    );
   }

The validate method takes as first argument the new state to validate. For instance if you have to validate an email, pass the email value.

The second argument is your field rules validation. You can omit this argument if you set the validation prop in your component.

Once you have extended ValidationComponent your class component can use one of the below methods :

Method / Member Output Benefits
this.validate(newState, fieldRules?) void This method ensures form validation within the object passed in argument. First argument is the new state to validate. Second argument is your field rules validation
this.isFormValid boolean This member indicates if the form is valid and if there are no errors.
this.isFieldInError(fieldName: string) boolean This method indicates if a specific field has an error. The field name will match with your React state
this.getErrorMessages(separator: string, separator = '\n') string This method returns the different error messages bound to your React state. The argument is optional, by default the separator is a \n. Under the hood a join method is used.
this.getErrorsInField(fieldName: string) string[] This method returns the error messages bound to the specified field. The field name will match with your React state. It returns an empty array if no error was bound to the field.
this.getErrorsForField(fieldName: string) Errors This method returns an Errors object containing 3 properties : fieldName: string, failedRules: string[], and messages: string[].
this.getFailedRules() {[fieldName: string]: string[]} This methods returns the failed rules of your form state
this.getFailedRulesInField(fieldName: string) string[] This method returns the failed rules bound to a specific field name.

Existing rules :

You will find bellow the default rules available in the library defaultRules.ts :

Rule Benefits
numbers Check if a state variable is a number.
email Check if a state variable is an email.
required Check if a state variable is not empty.
date Check if a state variable respects the date pattern. Ex: date: 'YYYY-MM-DD'
minlength Check if a state variable is greater than minlength.
maxlength Check if a state variable is lower than maxlength.
equalPassword Check if a state variable is equal to another value (useful for password confirm).
hasNumber Check if a state variable contains a number.
hasUpperCase Check if a state variable contains a upper case letter.
hasLowerCase Check if a state variable contains a lower case letter.
hasSpecialCharacter Check if a state variable contains a special character.

You can also extend these rules with your own custom rules :

  • For function component :
import { defaultRules } from 'react-simple-form-validator';

// extend default rules with my custom any rule
const customRules = { ...defaultRules, any: /^(.*)$/ };

const { isFieldInError, getErrorsInField, isFormValid } = useValidation({
    fieldRules: {
      email: { email: true },
      name: { required: true }
    },
    state: { firstName, lastName, email, civility },
    rules: customRules
  });
  • For class component :
import { defaultRules } from 'react-simple-form-validator';

// extend default rules with my custom any rule
const customRules = { ...defaultRules, any: /^(.*)$/ };

<FormTest rules={customRules} />

// Or override the custom rules into the super class child constructor

// FormTest.js
class FormTest extends ValidationComponent {
  constructor(props) {
    super({...props, rules: { ...defaultRules, any: /^(.*)$/ }});

    this.fieldRules = {
      email: { email: true },
      name: { required: true }
    };
  }
}

Existing messages :

The library also contains a defaultMessages.ts file which includes the errors label for a language locale.

  • For function component :
import { defaultMessages } from 'react-simple-form-validator';

const { isFieldInError, getErrorsInField, isFormValid } = useValidation({
  fieldRules: {
    email: { email: true },
    name: { required: true }
  },
  state: { email, name },
  messages: {
    en: {...defaultMessages.en, numbers: "error on numbers !"},
    fr: {...defaultMessages.fr,numbers: "erreur sur les nombres !"}
  }
});
  • For class component :

You can override the messages component React props :

import { defaultMessages } from 'react-simple-form-validator';

const messages = {
  en: {...defaultMessages.en, numbers: "error on numbers !"},
  fr: {...defaultMessages.fr,numbers: "erreur sur les nombres !"}
};

<FormTest messages={messages} />

Custom labels for error message interpolation :

You can add custom labels, which will be useful if you want to change the error messages label or translate it to the local language :

  • For function component :
import { defaultMessages } from 'react-simple-form-validator';

const { isFieldInError, getErrorsInField, isFormValid } = useValidation({
  fieldRules: {
    email: { email: true },
    name: { required: true }
  },
  state: { email, name },
  labels: {
    name: 'Name',
    email: 'E-mail',
    number: 'Phone number'
  }
});
  • For class component :
const labels = {
  name: 'Name',
  email: 'E-mail',
  number: 'Phone number'
};

<FormTest labels={labels} />

// Or override the custom labels into the super class child constructor

// FormTest.js
class FormTest extends ValidationComponent {
  constructor(props) {
    const labels = {
      name: 'Name',
      email: 'E-mail',
      number: 'Phone number'
    };
    super({...props, labels});

    this.fieldRules = {
      email: { email: true },
      name: { required: true }
    };
  }
}

Specify language locale :

You can specify the default custom local language :

  • For function component :
const { isFieldInError, getErrorsInField, isFormValid } = useValidation({
  fieldRules: {
    email: { email: true },
    name: { required: true }
  },
  state: { email, name },
  locale: 'fr'
});
  • For class component :
<FormTest locale="fr" />

// Or override the custom locale into the super class child constructor

// FormTest.js
class FormTest extends ValidationComponent {
  constructor(props) {
    super({...props, locale: 'fr'});

    this.fieldRules = {
      email: { email: true },
      name: { required: true }
    };
  }
}

3. Complete example

You can find a concrete Functional component example on FunctionForm.tsx (Typescript) :

Function Component:

import React, { Fragment, FunctionComponent, useState } from 'react';
import { defaultMessages, defaultRules, FieldsToValidate, useValidation } from 'react-simple-form-validator';

interface FunctionFormProps {
  validation: FieldsToValidate;
}

const FunctionForm: FunctionComponent<FunctionFormProps> = (props) => {
  const [touchedFields, setTouchedFields] = useState({
    civility: false,
    email: false,
    firstName: false,
    lastName: false
  });
  const [email, setEmail] = useState('');
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [civility, setCivility] = useState('');

  const { isFieldInError, getErrorsInField, isFormValid } = useValidation({
    fieldsRules: props.validation,
    state: { firstName, lastName, email, civility },
    rules: { ...defaultRules, customCivilityRule: /^(Mrs|Ms|Miss)$/ },
    messages: {
      ...defaultMessages,
      en: { ...defaultMessages['en'], customCivilityRule: 'Civility is incorrect (Mrs/Ms/Miss)' }
    }
  });

  const onBlurHandler = (event: React.FormEvent<HTMLElement>, field: string) =>
    setTouchedFields((prevFields) => ({ ...prevFields, [field]: true }));

  const formSubmitHandler = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log('Form values : ', civility, email, firstName, lastName);
    setFirstName('');
    setLastName('');
    setEmail('');
    setCivility('');
    setTouchedFields({ firstName: false, lastName: false, email: false, civility: false });
  };

  return (
    <Fragment>
      <h2>Functional Form</h2>
      <form onSubmit={formSubmitHandler}>
        <div className="form-control">
          <label htmlFor="gender">Civility</label>
          <input
            id="civility"
            type="text"
            onChange={(e) => setCivility(e.target.value)}
            onBlur={(e) => onBlurHandler(e, 'civility')}
            value={civility}
            placeholder="Seize a civility (Mrs or Ms or Miss)"
          />
          <p className="error-text">
            {touchedFields.civility && isFieldInError('civility') && getErrorsInField('civility').join('\n')}
          </p>
        </div>
        <div className="form-control">
          <label htmlFor="email">Email</label>
          <input
            id="email"
            type="email"
            onChange={(e) => setEmail(e.target.value)}
            onBlur={(e) => onBlurHandler(e, 'email')}
            value={email}
            placeholder="Seize an email"
          />
          <p className="error-text">
            {touchedFields.email && isFieldInError('email') && getErrorsInField('email').join('\n')}
          </p>
        </div>
        <div className="form-control">
          <label htmlFor="firstName">First name</label>
          <input
            id="firstName"
            type="text"
            onChange={(e) => setFirstName(e.target.value)}
            onBlur={(e) => onBlurHandler(e, 'firstName')}
            value={firstName}
            placeholder="Seize a first name"
          />
          <p className="error-text">
            {touchedFields.firstName && isFieldInError('firstName') && getErrorsInField('firstName').join('\n')}
          </p>
        </div>
        <div className="form-control">
          <label htmlFor="astName">Last Name</label>
          <input
            id="lastName"
            type="text"
            onChange={(e) => setLastName(e.target.value)}
            onBlur={(e) => onBlurHandler(e, 'lastName')}
            value={lastName}
            placeholder="Seize a last name"
          />
          <p className="error-text">
            {touchedFields.lastName && isFieldInError('lastName') && getErrorsInField('lastName').join('\n')}
          </p>
        </div>
        <div className="actions">
          <button disabled={!isFormValid}>Submit</button>
        </div>
      </form>
    </Fragment>
  );
};

export default FunctionForm;

Class component:

You can find a concrete Class component example on ClassForm.tsx (Typescript) :

import ValidationComponent, { ClassValidationProps, FormState } from 'react-simple-form-validator';
import { Fragment } from 'react';

class ClassForm extends ValidationComponent<ClassValidationProps, FormState> {
  constructor(props: ClassValidationProps) {
    super(props);

    this.state = {
      civility: '',
      email: '',
      firstName: '',
      lastName: '',
      touchedFields: { civility: false, firstName: false, lastName: false, email: false }
    };
  }

  onBlurHandler(event: React.FormEvent<HTMLElement>, field: string): void {
    this.setState({ touchedFields: { ...this.state.touchedFields, [field]: true } });
  }

  formSubmitHandler(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();

    const { civility, email, firstName, lastName } = this.state;

    console.log('Form Values : ', civility, email, firstName, lastName);

    this.setState({
      civility: '',
      email: '',
      firstName: '',
      lastName: '',
      touchedFields: { civility: false, firstName: false, lastName: false, email: false }
    });
  }

  render(): JSX.Element {
    return (
      <Fragment>
        <h2>Class based Form</h2>
        <form onSubmit={this.formSubmitHandler.bind(this)}>
          <div className="form-control">
            <label htmlFor="gender">Civility</label>
            <input
              id="civility"
              type="text"
              onChange={(e) => this.validate({ civility: e.target.value })}
              onBlur={(e) => this.onBlurHandler(e, 'civility')}
              value={this.state.civility}
              placeholder="Seize a civility (Mrs or Ms or Miss)"
            />
            <p className="error-text">
              {this.state.touchedFields.civility &&
                this.isFieldInError('civility') &&
                this.getErrorsInField('civility').join('\n')}
            </p>
          </div>
          <div className="form-control">
            <label htmlFor="email">Email</label>
            <input
              id="email"
              type="email"
              onChange={(e) => this.validate({ email: e.target.value })}
              onBlur={(e) => this.onBlurHandler(e, 'email')}
              value={this.state.email}
              placeholder="Seize an email"
            />
            <p className="error-text">
              {this.state.touchedFields.email &&
                this.isFieldInError('email') &&
                this.getErrorsInField('email').join('\n')}
            </p>
          </div>
          <div className="form-control">
            <label htmlFor="firstName">First name</label>
            <input
              id="firstName"
              type="text"
              onChange={(e) => this.validate({ firstName: e.target.value })}
              onBlur={(e) => this.onBlurHandler(e, 'firstName')}
              value={this.state.firstName}
              placeholder="Seize a first name"
            />
            <p className="error-text">
              {this.state.touchedFields.firstName &&
                this.isFieldInError('firstName') &&
                this.getErrorsInField('firstName').join('\n')}
            </p>
          </div>
          <div className="form-control">
            <label htmlFor="astName">Last Name</label>
            <input
              id="lastName"
              type="text"
              onChange={(e) => this.validate({ lastName: e.target.value })}
              onBlur={(e) => this.onBlurHandler(e, 'lastName')}
              value={this.state.lastName}
              placeholder="Seize a last name"
            />
            <p className="error-text">
              {this.state.touchedFields.lastName &&
                this.isFieldInError('lastName') &&
                this.getErrorsInField('lastName').join('\n')}
            </p>
          </div>
          <button disabled={!this.isFormValid}>Submit</button>
        </form>
      </Fragment>
    );
  }
}

export default ClassForm;

4. How to contribute

If you want to contribute to this project and make it better, your help is very welcome. Contributing is also a great way to learn more about social coding on Github, new technologies and and their ecosystems and how to make constructive, helpful bug reports, feature requests and the noblest of all contributions: a good, clean pull request.

Method

  1. Create a personal fork of the project on Github.
  2. Clone the fork on your local machine.
  3. Implement/fix your feature, comment your code.
  4. Follow the code style of this project, including indentation.
  5. Write or adapt tests as needed.
  6. Add or change the documentation as needed.
  7. Squash your commits into a single commit with git's interactive rebase. Create a new branch if necessary.
  8. Push your branch to your fork on Github, the remote origin.
  9. From your fork open a pull request in the correct branch. Target the project's master branch.

Install the project

Run this command to install the library dependencies :

npm install

Build the library :

npm run build 

Launch tests :

# single run
npm run test 

# watch mode
npm run test:watch

Run the React JS example project :

cd examples/react-js && yarn install

Start the example project :

yarn start
  • If you modify the library don't forget to re-run npm run build to apply modifications into the example project.

About

A simple library to validate your form fields with React or React Native

Resources

Stars

Watchers

Forks

Packages

No packages published