The goal of this library is to implement the Constraint Validation API in React while not getting in your way to do it.
Of the existing react form libraries, the use of the constraint validation api is woefully inadequate. Using this API properly is important for accessibility - you need to let the user agent know what is going on.
You can import the Form
and Input/Select/TextArea
exports from this library.
These are wrappers around <form/>
and <input/select/textarea>
elements. Any additional props you provide to these elements will be passed down to the underlying form/input/select/textarea
element. If you need to, you can also access the underlying element by passing a ref
.
Input
elements must be children of a Form
element. Under the covers, this library uses context to keep track of all fields on the form and will validate all of them if the form is submitted.
A Validator
component is also provided which attempts to make using the api a bit easier. This is a container element that uses a render prop which is called with ({ error, valid, invalid, validated })
. This routine recursively traverses any provided props to replace input/select/textarea
elements with the exports from this library, so it will duplicate any work already done up to that point.
import { Form } from '@tswaters/react-form-validation'
const example = () => {
return <Form />
}
-
ref - you can pass a
ref
to get a reference to the underlying<form>
element -
any additional props will be passed down to the underlying
<form>
element -
NOTE: onSubmit will only be called if the form passes validation!
Input/Select/TextArea take all the same props.
import { Input, Select, TextArea } from '@tswaters/react-form-validation'
const example = () => (
<>
<Input // Select | TextArea
validation={oneOfType([arrayOf(func), func])}
other={oneOfType([arrayOf(string), string])}
recheck={bool}
blur={bool}
change={bool}
onError={func}
onInvalid={func}
onValid={func}
onValidated={func}
/>
</>
)
-
validation (field, others) => Promise<void|string|Error>
An function, or array of functions. Will be called for validation with two parameters (
field
- reference to theinput/select/textarea
;others
- an array of all form inputs). You can return:- an error
- a string (this will be interpreted as an error)
- null/undefined (returning nothing signifies validation passes)
- throwing an error will be interpreted as failing validation
-
other string|string[] - provide the name or id of another element on the form. When validating this element, the other(s) will also have their validation routines called, assuming they have not yet been touched.
-
blur bool - validate this field on input blur
-
change bool - validate this field on input change
-
recheck bool - if recheck is passed as TRUE, once a form field is validated it will be revalidated on any change.
-
onError (Error|null) => void - will be called if there is a validation error. This will always be an error object (so check
message
/code
), ornull
if there is no error. -
onInvalid (bool) => void will be called after validation with a bool indicating the form field is invalid
-
onValid (bool) => void will be called after validation with a bool indicating the form field is valid
-
onValidated (bool) => void will be called after an input is validated.
Any additional props will be passed down to the underlying input/select/textarea
element
import { Validator } from '@tswaters/react-form-validation'
const example = () => (
<Validator recheck blur>
{({ error, valid, invalid }) => (
<>
<label htmlFor="my-value">My Value</label>
<input id="my-value" name="my-value" type="text" required />
{valid && <div>valid</div>}
{invalid && <div>invalid</div>}
{error && <div>{error.message}</div>}
</>
)}
</Validator>
)
The validator will find & replace all input/select/textarea
elements with Input/Select/TextArea
.
The render prop, ({ error, valid, invalid, validated })
will be updated with any feedback from the constraint validation API.
Any props provided to validator
will be passed through to the underlying Input/Select/TextArea
elements.
const FormGroup = ({ id, label, ...rest }) => {
return (
<Validator recheck blur>
{({ error, validated }) => (
<div className={`form-group ${validated ? 'was-validated' : ''}`}>
<label className="control-label" htmlFor={id}>
{label}
</label>
<input id={id} className="form-control" {...rest} />
{error && <div className="invalid-feedback">{error.message}</div>}
</div>
)}
</Validator>
)
}
const LoginForm = () => {
return (
<Form onSubmit={(e) => e.preventDefault()}>
<FormGroup id="user-name" name="user-name" label="User Name" required />
<FormGroup
id="password"
name="password"
label="Password"
type="password"
required
/>
<button type="submit">Submit</button>
</Form>
)
}
You can provide validations to the <Input/Select/TextArea>
element and they will be called as part of validating the element.
Validation routines must be synchronous. Validation will be considered failed with the following returns:
-
a string - the error returned will be
new Error(returnValue)
-
an error - the error returned will be
returnValue
Otherwise, the validation is considered to have succeeded.
import { Form, Validator } from '@tswaters/react-form-validation'
const MyForm = () => {
const [error, setError] = useState(null)
const [response, setResponse] = useState(null)
const validation = useCallback((inputRef) => {
if (restrictedWords.includes(inputRef.value))
return new Error('cant use restricted words')
}, [])
const handleSubmit = useCallback((e) => {
e.preventDefault()
console.log('completely valid, no restricted words here!')
}, [])
return (
<Form onSubmit={handleSubmit} noValidate>
<Validator change validation={validation}>
{({ error }) => {
return (
<>
<input name="user-name" required />
{error && <div>{error.message}</div>}
</>
)
}}
</Validator>
<button type="submit">Submit</button>
</Form>
)
}
Any errors for non-custom validation routines will be returned by the browser, so based upon the user's OS/browser language settings, you'll get different translated error messages, for better or worse.
message
will be populated with the browser-defined error, and a code
is also set that identifies the type of error that occured. As an example, an error raised from a required input not being provided might look something like this:
{
"message": "Please provide a value",
"code": "valueMissing"
}
You can override the translations by inspecting the code and providing the correct translation:
const ErrorDisplay = error => {
const [t] = useTranslation() // or however else you get a translation function
return error ? t(`validation-error-${error.code}` : null
}
For custom error messages, error.message
will be whatever you returned or threw back and the code will be customError
-
make sure the tree passed via
<Validator />
is pretty simple. Validator recursively traverses the tree and replaces html inputs with exports from this library. -
validation functions should be memoized lest any change invoke a re-render (wrap functions with
useCallback
, or an array of functions withmemo
)
-
only the first error for a given input element will be returned
-
there's gunna be some weirdness with
code
returning incorrectly if multiple constraint errors are raised.