react-jsonschema-form-validation
v0.7.0
Published
Simple form validation using JSON Schema and AJV
Downloads
619
Maintainers
Readme
React JSON Schema Form Validation
Validate forms with powerful JSON Schema and Ajv !
This library links JSON Schema, Ajv and Form to :
- describe data model with JSON Schema
- validate the form data with Ajv
- display & customize error messages
- use your own graphical components to build friendly user forms.
Why RJFV ?
- Simplicity (no extraneous features, just what you need)
- Performance (AJV is extremely fast :zap:)
- Actively maintained
- The simplest react JSON Schema validation module ever published on npm ! :v:
Other JSON Schema validation modules published on NPM are often complex, with too much features. That's why we created react-jsonschema-form-validation. You'll just need a schema, a form, some fields, and your data. Nothing more. it's S I M P L E
Our philosophy :
- focused on validation, not UI
- highly customizable
- minimal CSS (15 lines) : just a red color to show error message (can be overriden)
Installation
npm install react-jsonschema-form-validationyarn add react-jsonschema-form-validationThen import the packaged stylesheet — the minimal CSS mentioned above (a red color for error messages, easily overridden):
import 'react-jsonschema-form-validation/dist/react-jsonschema-form-validation.css';A minified variant is also shipped:
react-jsonschema-form-validation/dist/react-jsonschema-form-validation.min.css.
Getting started
Import modules:
import React, { useState } from 'react';
import { Field, FieldError, Form } from 'react-jsonschema-form-validation';Define your JSON-Schema:
const demoSchema = {
type: 'object',
properties: {
email: { type: 'string', format: 'email' },
},
required: [
'email',
],
};Declare your Form, Field and FieldError components.
Pass your schema to the Form props.
Example using a functional component and React hooks:
const DemoForm = (props) => {
const [formData, setFormData] = useState({ email: '' });
const handleChange = (newData) => {
// newData is a copy of the object formData with properties (and nested properties)
// updated using immutability pattern for each change occured in the form.
setFormData(newData);
}
const handleSubmit = () => {
const { doWhateverYouWant } = props;
doWhateverYouWant(formData); // Do whatever you want with the form data
}
return (
<Form
data={formData}
onChange={handleChange}
onSubmit={handleSubmit}
schema={demoSchema}
>
<label>Email :</label>
<Field
name="email"
value={formData.email}
/>
<FieldError name="email" />
<button type="submit">Submit</button>
</Form>
);
}Same example using a class component:
class DemoForm extends PureComponent {
state = {
formData: {
email: '',
},
}
handleChange = (newData) => {
// newData is a copy of the object formData with properties (and nested properties)
// updated using immutability pattern for each change occured in the form.
this.setState({ formData: newData });
}
handleSubmit = () => {
const { doWhateverYouWant } = this.props;
const { formData } = this.state;
doWhateverYouWant(formData); // Do whatever you want with the form data
}
render() {
<Form
data={this.state.formData}
onChange={this.handleChange}
onSubmit={this.handleSubmit}
schema={demoSchema}
>
<label>Email :</label>
<Field
name="email"
value={formData.email}
/>
<FieldError name="email" />
<button type="submit">Submit</button>
</Form>
}
}🎵 That's all folks !
Custom error messages
Pass an errorMessages map (keyword → function) to <Form> — or to a
single <FieldError> — to replace AJV's raw messages. Each function
receives the AJV error, which carries the current value of the offending
field as error.data, so messages can quote what the user actually typed:
<Form
data={formData}
errorMessages={{
minLength: (e) => `"${e.data}" is too short (min ${e.params.limit} characters)`,
format: (e) => `"${e.data}" is not a valid ${e.params.format}`,
required: () => 'This field is required',
}}
onChange={handleChange}
onSubmit={handleSubmit}
schema={demoSchema}
>
<Field name="email" value={formData.email} />
<FieldError name="email" />
<button type="submit">Submit</button>
</Form>What error.data contains, per error type:
- value-level keywords (
minLength,format,enum,const,pattern,minimum, … including$datareferences): the current value of the field; required: the parent object missing the property (AJV reportsrequiredon the parent — the missing value itself does not exist). Usee.params.missingPropertyfor the field name.
Errors also expose error.schema (the failing keyword's value, e.g. 5
for minLength: 5) and error.parentSchema (the enclosing subschema).
Serialization & logging — with
verbose, arequirederror carries the entire parent object inerror.data. AJSON.stringify(errors)shipped to monitoring or logs can therefore exfiltrate sensitive sibling fields (a password living next to the missing property, for instance), and cyclic data would makestringifythrow. Log a projection of chosen fields (field,keyword,message) rather than raw error objects.
Note — these properties come from AJV's
verboseoption, enabled on the default instance. If you pass your own instance through theajvprop of<Form>, setverbose: trueyourself to benefit fromerror.data.
Accessibility
<Field> and <FieldError> wire the essential ARIA attributes for you — no
extra props needed:
<FieldError>renders withrole="alert"by default, so assistive technologies announce the message as soon as it appears. Pass your ownroleprop to override it.<Field>setsaria-invalid="true"on the rendered element when the field is invalid and revealed (touched, or the form was submitted) — the same condition as the visual error styling, so nothing is announced on a pristine form. It is a plain default: an explicitaria-invalidprop wins.aria-describedbyis wired automatically: each<FieldError>registers itsidin the parent<Form>, and the matching<Field>references those ids once the field is revealed. The default id is deterministic —jfv<N>-error-<name>, wherejfv<N>identifies the<Form>instance so two forms on the same page never collide. A customidprop on<FieldError>is followed automatically.- An
aria-describedbyyou pass to<Field>yourself is merged, not replaced: your ids come first (e.g. a hint text), the error ids after. - On a failed submit, keyboard focus moves to the first invalid field
(disable with
scrollToError={false}on<Form>).
<span id="email-hint">We never share your email.</span>
<Field name="email" aria-describedby="email-hint" value={formData.email} />
<FieldError name="email" />
{/* Once the field is touched (or the form submitted) and invalid,
the input renders:
aria-invalid="true" aria-describedby="email-hint jfv1-error-email" */}The default error id can be computed with the exported helper (formId is
available on the form context, see useFormContext):
import { getFieldErrorId } from 'react-jsonschema-form-validation';
getFieldErrorId('jfv1', 'email'); // 'jfv1-error-email'Known limitations:
- Rendering several
<FieldError>with the samenamein one form produces duplicate default ids (invalid HTML). Overrideidon all but one of them. - A
namecontaining spaces cannot be referenced through the generatedaria-describedby(it is a space-separated id list). Give such a<FieldError>a customid.
Reading the form state: useFormContext
Everything <Form> knows is available to its descendants through the
useFormContext() hook (a legacy render-prop helper, withFormContext(cb),
also exists). Both throw a descriptive error when used outside a <Form>.
import { useFormContext } from 'react-jsonschema-form-validation';Main context values:
| Name | Description |
| --- | --- |
| errors | Current validation errors (FormattedError[], each with a normalized field path) |
| valid | true when the data matches the schema. Validation is throttled (200 ms by default, throttleDuration prop on <Form>), so valid can lag one beat behind the latest change |
| isSubmitted | true once a submit has been attempted (reset after a successful submit, unless resetOnSubmit={false}) |
| touchedFields | Names of the fields that have been blurred |
| reset() | Resets errors, touchedFields and isSubmitted to their initial state (called automatically after a successful submit unless resetOnSubmit={false}) |
| getFieldErrors(names) | Errors for one or several field paths (wildcards like emails.* work) |
| isFieldInvalid(names) | true if any of the given fields has an error |
| isFieldTouched(names) | true if any of the given fields was touched |
| isTouched() | true if at least one field was touched |
| touch(names) | Marks fields as touched |
| handleFieldChange(event) or handleFieldChange(name, value) | Applies a change programmatically |
| formId | Unique id of the <Form> instance (see Accessibility) |
| errorMessages | The errorMessages map passed to <Form>, if any |
(The remaining values — fieldErrorsVersion, getFieldErrorDescribedBy,
registerFieldError, unregisterFieldError — are internal wiring between
<Field> and <FieldError>.)
Disable the submit button while the form is invalid:
const SubmitButton = ({ children }) => {
const { valid } = useFormContext();
return (
<button type="submit" disabled={!valid}>
{children}
</button>
);
};Show an error summary after a failed submit:
const ErrorSummary = () => {
const { errors, isSubmitted } = useFormContext();
if (!isSubmitted || !errors.length) return null;
return (
<ul>
{errors.map((error) => (
<li key={`${error.field}-${error.keyword}`}>
{`${error.field}: ${error.message}`}
</li>
))}
</ul>
);
};Use them anywhere inside the <Form>:
<Form data={formData} onChange={handleChange} onSubmit={handleSubmit} schema={demoSchema}>
<ErrorSummary />
<Field name="email" value={formData.email} />
<FieldError name="email" />
<SubmitButton>Submit</SubmitButton>
</Form>TypeScript
The library is fully typed — .d.ts declarations ship with the package, no
@types/... required.
Most relevant exported types:
import type {
FormProps, // generic: FormProps<T, C> — T = data shape, C = wrapper element
FieldProps, // generic: FieldProps<C> — C = underlying component
FieldErrorProps, // generic: FieldErrorProps<C>
FormContextValue,
FormattedError,
ErrorMessageFn,
ErrorMessagesMap,
} from 'react-jsonschema-form-validation';Examples
We’ve got many examples, from the most simple to the most advanced.
Live examples are available : here
Documentation
📃 Check out our documentation : here
Changelog
See CHANGELOG.md for release notes.
Licence
MIT
About us
📬 contact : [email protected]
follow us : @53jsdev
github repos : /53js
🚀 website : 53js.fr
