Socket
Socket
Sign inDemoInstall

react-form-with-constraints

Package Overview
Dependencies
6
Maintainers
1
Versions
77
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    react-form-with-constraints

Simple form validation for React


Version published
Maintainers
1
Install size
306 kB
Created

Changelog

Source

v0.19.1 (2022/07/22)

  • Switch from CodeSandbox to StackBlitz

Readme

Source

react-form-with-constraints

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

Simple form validation for React

  • Installation: npm install react-form-with-constraints
  • CDN: https://unpkg.com/react-form-with-constraints/dist/

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

Keywords

FAQs

Last updated on 21 Jul 2022

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc