Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

react-form-with-constraints

tkrotoff2.5kMIT0.19.1TypeScript support: included

Simple form validation for React

react, form, validation, form-validation, input-validation, constraints, html5

readme

react-form-with-constraints

npm version Node.js CI codecov Bundle size Prettier Airbnb Code Style

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 type="submit">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 styling with npm package react-form-with-constraints-bootstrap
  • 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({ 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>): 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-bootstrap 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 11. It also needs a polyfill such as core-js to support IE 11, 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).

Notes

changelog

v0.19.1 (2022/07/22)

  • Switch from CodeSandbox to StackBlitz

v0.19.0 (2022/07/21)

  • Fix React 18 types (#58)
  • Use React 18 in examples
  • Update most npm packages when possible
  • Switch from Puppeteer to Playwright
  • Remove Yarn, use npm workspaces instead
  • Use Lerna only for npm run version

v0.18.0 (2021/05/20)

Breaking Changes

  • Upgrade Bootstrap from v4 to v5

v0.17.0 (2021/05/16)

Breaking Changes

  • Rename react-form-with-constraints-bootstrap4 to react-form-with-constraints-bootstrap

    I won't have the man power to maintain Bootstrap 4 & 5

v0.16.1 (2021/05/06)

Fixes

  • Fix jscodeshift: remove console.assert() from bundle

v0.16.0 (2020/10/22)

Breaking Changes

  • Drop IE 10 support (IE 11 still supported)
  • Use Array.flat(Infinity): Node.js >= 12 required
  • Use TypeScript 3.7 "asserts condition"

Features

  • Update npm packages
  • More ESLint and Stylelint plugins

v0.15.2 (2020/10/20)

Fixes

  • Set field.element as soon as possible

v0.15.0 (2020/03/05)

Breaking Changes

  • Dissociate emitSync and emitAsync
    • resetFields() does not return a Promise anymore
    • fieldWillValidate(), fieldDidValidate() and fieldDidReset() are sync instead of async: this might affect your tests

Features

  • Improve README
  • No need for downlevelIteration anymore

Fixes

  • Fix .map files

v0.14.2 (2020/02/28)

Features

  • Enable Airbnb ESLint restricted syntax, should reduce the bundle size
  • No default export, see Avoid Export Default

v0.14.1 (2020/02/28)

Features

  • Update npm packages

v0.14.0 (2019/09/25)

Breaking Changes

  • Make DisplayFields work with React Native. You will need to write:
    • HTML: <pre>Fields = <DisplayFields /></pre>
    • React Native: <Text>Fields = <DisplayFields /></Text>

Features

  • Upgrade npm packages
  • componentWillMount => UNSAFE_componentWillMount => componentDidMount
  • componentWillUpdate => UNSAFE_componentWillUpdate => componentDidUpdate
  • Prettier + ESLint - TSLint
  • Add Field.element: HTMLInput | TextInput | undefined, see #41

Fixes

  • Fix react-form-with-constraints-tools package.json "main": "lib-es5/index.js"

v0.13.0 (2019/06/25)

Features

  • Specify npm dependencies versions instead of using "latest"
  • Upgrade to Material-UI >= 4.1.1
  • Upgrade to Expo SDK 33
  • Remove console.* from packages before publishing
  • Prettier
  • ESLint
  • Stylelint
  • Husky

v0.12.0 (2019/03/21)

Features

  • Update examples to use hooks
  • Remove *.js.gz files
  • Upgrade NPM packages

v0.11.0 (2019/01/05)

Fixes

  • Disable esModuleInterop (import * as React vs import React) to increase compatibility with TypeScript users, see b18d61
  • Fix react-form-with-constraints-bootstrap4 for IE 10: does no support inheritance with static properties, see 225cb9

v0.10.0 (2018/10/16)

Features

  • Input is-pending class
  • resetFields()
  • validateFieldsWithoutFeedback()
  • Use tree shaking and generate ES2017 files

Breaking Changes

  • reset() renamed to resetFields()

Fixes

  • Ignore HTML elements without ValidityState instead of type

v0.9.3 (2018/09/12)

Features

  • Use PropTypes.instanceOf() instead of PropTypes.object
  • Improve typings

Fixes

  • Ignore HTML elements without type

v0.9.2 (2018/07/13)

Features

  • Upgrade to Bootstrap 4.1.2
  • Add a README.md for every npm package

v0.9.1 (2018/07/06)

Features

  • <Input> component for field styling
  • Material-UI integration with react-form-with-constraints-material-ui
  • hasFeedbacks() to implement reset button

Breaking Changes

  • FieldFeedback uses <span style="display: block"> instead of <div> in order to be a child of <p>
  • HTML: styling done on FieldFeedback with classes props instead of FormWithConstraints.fieldFeedbackClassNames
  • React Native: styling done on FieldFeedback with theme props instead of FormWithConstraints.fieldFeedbackStyles
  • Rename TypeScript Input to InputElement

v0.8.0 (2018/04/26)

Features

  • Async support
  • Rewrite to allow nested FieldFeedbacks
  • Strip console.* in production thanks to rollup-plugin-strip
  • Add reset(), see #22

Breaking Changes

Fixes

  • Fix computeFieldFeedbackKey() implementation
  • Fix possible crash with React Native, see 03d72e1

v0.7.1 (2017/11/27)

Fixes

v0.7.0 (2017/11/26)

Features

  • React Native support