🔔 Alert..!! Get 2 Month Free Cloud Hosting With $200 Bonus From Digital Ocean ACTIVATE DEAL

A simple, lightweight, form validation component for both React and React Native.

Form React React Native

Documentation

react-form-with-constraints

npm version Build Status codecov gzip size

Simple form validation for React

Check the changelog for breaking changes and fixes between releases.

Introduction: what is HTML5 form validation?

⚠️ Client side validation is cosmetic, you should not rely on it to enforce security

<form>   <label for="email">Email:</label>   <input type="email" id="email" required>   <button>Submit</button> </form>

input required input type="email"

The required HTML5 attribute specifies that the user must fill in a value, type="email" checks that the entered text looks like an email address.

Resources:

What react-form-with-constraints brings

  • Minimal API and footprint
  • Unobtrusive: easy to adapt regular React code
  • HTML5 error messages personalization: <FieldFeedback when="valueMissing">My custom error message</FieldFeedback>
  • Custom constraints: <FieldFeedback when={value => ...}>
  • Warnings and infos: <FieldFeedback ... warning>, <FieldFeedback ... info>
  • Async validation
  • No dependency beside React (no Redux, MobX...)
  • Re-render only what's necessary
  • Easily extendable
  • Bootstrap 4 styling with npm package react-form-with-constraints-bootstrap4
  • Material-UI integration with npm package react-form-with-constraints-material-ui
  • Support for React Native with npm package react-form-with-constraints-native
  • ...
<input type="password" name="password"        value={this.state.password} onChange={this.handleChange}        required pattern=".{5,}" /> <FieldFeedbacks for="password">   <FieldFeedback when="valueMissing" />   <FieldFeedback when="patternMismatch">     Should be at least 5 characters long   </FieldFeedback>   <FieldFeedback when={value => !/\d/.test(value)} warning>     Should contain numbers   </FieldFeedback>   <FieldFeedback when={value => !/[a-z]/.test(value)} warning>     Should contain small letters   </FieldFeedback>   <FieldFeedback when={value => !/[A-Z]/.test(value)} warning>     Should contain capital letters   </FieldFeedback> </FieldFeedbacks>

Examples

How it works

The API works the same way as React Router:

<Router>   <Route exact path="/" component={Home} />   <Route path="/news" component={NewsFeed} /> </Router>

It is also inspired by AngularJS ngMessages.

If you had to implement validation yourself, you would end up with a global object that tracks errors for each field. react-form-with-constraints works similarly. It uses React context to share the FieldsStore object across FieldFeedbacks and FieldFeedback.

API

The API reads like this: "for field when constraint violation display feedback", example:

<FieldFeedbacks for="password">   <FieldFeedback when="valueMissing" />   <FieldFeedback when="patternMismatch">Should be at least 5 characters long</FieldFeedback> </FieldFeedbacks>
for field "password"   when constraint violation "valueMissing"    display <the HTML5 error message (*)>   when constraint violation "patternMismatch" display "Should be at least 5 characters long" 

(*) element.validationMessage

Async support works as follow:

<FieldFeedbacks for="username">   <Async     promise={checkUsernameAvailability} /* Function that returns a promise */     then={available => available ?       <FieldFeedback key="1" info style={{color: 'green'}}>Username available</FieldFeedback> :       <FieldFeedback key="2">Username already taken, choose another</FieldFeedback>       // Why key=*? Needed otherwise React gets buggy when the user rapidly changes the field     }   /> </FieldFeedbacks>

Trigger validation:

function MyForm() {   const form = useRef(null);    async function handleChange(e) {     const target = e.target;      // Validates only the given fields and returns Promise<Field[]>     await form.current.validateFields(target);   }    async function handleSubmit(e) {     e.preventDefault();      // Validates the non-dirty fields and returns Promise<Field[]>     await form.current.validateForm();      if (form.current.isValid()) console.log('The form is valid');     else console.log('The form is invalid');   }    return (     <FormWithConstraints ref={form} onSubmit={handleSubmit} noValidate>       <input         name="username"         onChange={handleChange}         required minLength={3}       />       <FieldFeedbacks for="username">         <FieldFeedback when="tooShort">Too short</FieldFeedback>         <Async           promise={checkUsernameAvailability}           then={available => available ?             <FieldFeedback key="1" info style={{color: 'green'}}>Username available</FieldFeedback> :             <FieldFeedback key="2">Username already taken, choose another</FieldFeedback>           }         />         <FieldFeedback when="*" />       </FieldFeedbacks>     </FormWithConstraints>   ); }

Important note:

If a field (i.e an <input>) does not have a matching FieldFeedbacks, the library won't known about this field (and thus won't perform validation). The field name should match FieldFeedbacks.for:

<input name="MY_FIELD" ...> <FieldFeedbacks for="MY_FIELD">   ... </FieldFeedbacks>


  • FieldFeedbacks

    • for: string => reference to a name attribute (e.g <input name="username">), should be unique to the current form
    • stop?: 'first' | 'first-error' | 'first-warning' | 'first-info' | 'no' => when to stop rendering FieldFeedbacks, by default stops at the first error encountered (FieldFeedbacks order matters)

    Note: you can place FieldFeedbacks anywhere, have as many as you want for the same field, nest them, mix them with FieldFeedback... Example:

    <input name="username" ... />  <FieldFeedbacks for="username" stop="first-warning">   <FieldFeedbacks>     <FieldFeedback ... />     <Async ... />     <FieldFeedbacks stop="first-info">       ...     </FieldFeedbacks>   </FieldFeedbacks>    <FieldFeedback ... />   <Async ... /> </FieldFeedbacks>  <FieldFeedbacks for="username" stop="no">   ... </FieldFeedbacks>
  • FieldFeedback

    • when?:
      • ValidityState as a string => HTML5 constraint violation name
      • '*' => matches any HTML5 constraint violation
      • 'valid' => displays the feedback only if the field is valid
      • (value: string) => boolean => custom constraint
    • error?: boolean => treats the feedback as an error (default)
    • warning?: boolean => treats the feedback as a warning
    • info?: boolean => treats the feedback as an info
    • children => what to display when the constraint matches; if missing, displays the HTML5 error message if any
  • Async<T> => Async version of FieldFeedback (similar API as react-promise)

    • promise: (value: string) => Promise<T> => a promise you want to wait for
    • pending?: React.ReactNode => runs when promise is pending
    • then?: (value: T) => React.ReactNode => runs when promise is resolved
    • catch?: (reason: any) => React.ReactNode => runs when promise is rejected
  • FormWithConstraints

    • validateFields(...inputsOrNames: Array<Input | string>): Promise<Field[]> => Should be called when a field changes, will re-render the proper FieldFeedbacks (and update the internal FieldsStore). Without arguments, all fields ($('[name]')) are validated.

    • validateFieldsWithoutFeedback(...inputsOrNames: Array<Input | string>): Promise<Field[]> => Validates only all non-dirty fields (won't re-validate fields that have been already validated with validateFields()), If you want to force re-validate all fields, use validateFields(). Might be renamed to validateNonDirtyFieldsOnly() or validateFieldsNotDirtyOnly() in the future?

    • validateForm(): Promise<Field[]> => Same as validateFieldsWithoutFeedback() without arguments, typically called before to submit the form. Might be removed in the future?

    • isValid(): boolean => should be called after validateFields(), validateFieldsWithoutFeedback() or validateForm(), indicates if the fields are valid

    • hasFeedbacks(): boolean => indicates if any of the fields have any kind of feedback

    • resetFields(...inputsOrNames: Array<Input | string>): Promise<Field[]> => Resets the given fields and re-render the proper FieldFeedbacks. Without arguments, all fields ($('[name]')) are reset.

    • Field =>

      {   name: string;   validations: { // FieldFeedbackValidation[]     key: number;     type: 'error' | 'warning' | 'info' | 'whenValid';     show: boolean | undefined;   }[];   isValid: () => boolean }
  • Input

    If you want to style <input>, use <Input> instead: it will add classes is-pending, has-errors, has-warnings, has-infos and/or is-valid on <input> when the field is validated.

    Example: <Input name="username" /> can generate <input name="username" class="has-errors has-warnings">

    FYI react-form-with-constraints-bootstrap4 and react-form-with-constraints-material-ui already style the fields to match their respective frameworks.

Browser support

react-form-with-constraints needs ValidityState which is supported by all modern browsers and IE >= 10. It also needs a polyfill such as core-js to support IE >= 10, see React JavaScript Environment Requirements.

You can use HTML5 attributes like type="email", required, minlength...

<label htmlFor="email">Email</label> <input type="email" name="email" id="email"        value={this.state.email} onChange={this.handleChange}        required /> <FieldFeedbacks for="email">   <FieldFeedback when="*" /> </FieldFeedbacks>

...and/or rely on when functions:

<label htmlFor="email">Email</label> <input name="email" id="email"        value={this.state.email} onChange={this.handleChange} /> <FieldFeedbacks for="email">   <FieldFeedback when={value => value.length === 0}>Please fill out this field.</FieldFeedback>   <FieldFeedback when={value => !/\S+@\S+/.test(value)}>Invalid email address.</FieldFeedback> </FieldFeedbacks>

In the last case you will have to manage translations yourself (see SignUp example).

How to consume the npm packages?

ESNext (currently ES2018) + ES modules

Files inside lib/ (package.json "module": "lib/index.js").

A recent browser or Node.js is required or you will need to transpile the react-form-with-constraints source code using Babel (or TypeScript tsc).

Several advantages:

  • The combine use of "sideEffects": false with "module": ... generates a smaller bundle thanks to tree shaking
  • You can transpile react-form-with-constraints source code with your Babel's preset-env and Browserslist configuration

For this to work, do not exclude node_modules from your webpack configuration, example:

// webpack.config.js module: {   rules: [     {       test: /\.(js|jsx?)$/,        // See [Enable babel-preset-env for node_modules that target newer Node versions](https://github.com/facebook/create-react-app/issues/1125)       // See [Create React App 2.0: "You can now use packages written for latest Node versions without breaking the build"](https://reactjs.org/blog/2018/10/01/create-react-app-v2.html)       // See ["If you have to exclude node_modules/, how do you get babel to polyfill/transform code in 3rd party code?"](https://github.com/webpack/webpack/issues/6544#issuecomment-417108242)       // See [Compile dependencies with babel-preset-env](https://github.com/facebook/create-react-app/pull/3776)       //exclude: /node_modules/,       exclude: /\/core-js/,        loader: 'babel-loader',       options: {         // See https://github.com/facebook/create-react-app/blob/v2.1.0/packages/react-scripts/config/webpack.config.dev.js#L284         compact: false       }     }   ] }

You probably want to configure Babel with sourceType: 'unambiguous':

// babel.config.js module.exports = {   // See https://github.com/facebook/create-react-app/blob/v2.1.0/packages/babel-preset-react-app/dependencies.js#L64   // See [Add Babel config sourceType: 'unambiguous' for dependencies](https://github.com/facebook/create-react-app/pull/5052)   sourceType: 'unambiguous',    presets: [     [       '@babel/preset-env',       {         useBuiltIns: 'usage',         corejs: 3       }     ],     '@babel/preset-react'   ],   plugins: [     '@babel/plugin-proposal-class-properties',     '@babel/plugin-proposal-object-rest-spread'   ] };

ES5 + CommonJS

Classic ES5 transpilation, files inside lib-es5/ (package.json "main": "lib-es5/index.js"). No tree shaking.

UMD (Universal Module Definition) + ES5

Files inside dist/. Typical use is with <script src="react-form-with-constraints.production.min.js"> inside your index.html.

A good use case is CodePen, files are generated by Rollup.

Notes


You May Also Like