Simple form validation for React
npm install react-form-with-constraints
Check the changelog for breaking changes and fixes between releases.
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>
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:
<FieldFeedback when="valueMissing">My custom error message</FieldFeedback>
<FieldFeedback when={value => ...}>
<FieldFeedback ... warning>
, <FieldFeedback ... info>
react-form-with-constraints-bootstrap4
react-form-with-constraints-material-ui
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>
CodePen basic Password example: https://codepen.io/tkrotoff/pen/BRGdqL (CodeSandbox version)
React Native example (React classes):
iOS | Android |
---|---|
Other examples from the examples directory:
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
.
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"
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>
for: string
=> reference to a name
attribute (e.g <input name="username">
), should be unique to the current formstop?: 'first' | 'first-error' | 'first-warning' | 'first-info' | 'no'
=> when to stop rendering FieldFeedback
s, by default stops at the first error encountered (FieldFeedback
s 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>
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 constrainterror?: boolean
=> treats the feedback as an error (default)warning?: boolean
=> treats the feedback as a warninginfo?: boolean
=> treats the feedback as an infochildren
=> what to display when the constraint matches; if missing, displays the HTML5 error message if anyAsync<T>
=> Async version of FieldFeedback
(similar API as react-promise)
promise: (value: string) => Promise<T>
=> a promise you want to wait forpending?: React.ReactNode
=> runs when promise is pendingthen?: (value: T) => React.ReactNode
=> runs when promise is resolvedcatch?: (reason: any) => React.ReactNode
=> runs when promise is rejectedvalidateFields(...inputsOrNames: Array<Input | string>): Promise<Field[]>
=> Should be called when a field
changes, will re-render the proper FieldFeedback
s (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 FieldFeedback
s. Without arguments, all fields ($('[name]')
) are reset.
Field
=>
{
name: string;
validations: { // FieldFeedbackValidation[]
key: number;
type: 'error' | 'warning' | 'info' | 'whenValid';
show: boolean | undefined;
}[];
isValid: () => boolean
}
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.
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).
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:
"sideEffects": false
with "module": ...
generates a smaller bundle thanks to tree shakingFor 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'
]
};
Classic ES5 transpilation, files inside lib-es5/
(package.json "main": "lib-es5/index.js"
). No tree shaking.
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.
Author: tkrotoff
Official Website: https://github.com/tkrotoff/react-form-with-constraints
#reactjs #javascript