With the package you don't need to re-create form validation logic every time. Instead you take full advantage of HTML5 Constraint Validation API. Besides the package expose no custom input components, instead it provides wrapper InputGroup
, which binds inputs (according to configuration) in the children DOM nodes. Thus you can go with both HTML controls and 3rd-party input components.
- Input implementation agnostic, can be used any arbitrary input controls (HTML or 3rd-party React components)
- Standard HTML5 syntax for validation constraints (like
<input type="email" required maxLength="50" ... />
) - Can be extended with custom validators (again by using standard HTML5 API)
- Can be setup for custom validation messages per
input
/ValidityState.Property
- Form and input groups expose validity states, so you can use it to toggle scope error messages.
- Form and input groups expose reference to own instances for children DOM nodes. So you can, for example, subscribe for
input
event and run input group validation (e.g. see On-the-fly validation) - Can maintain form state both ways: with React Context and Redux
With Form
we define scope of the form. The component exposes parameters valid
, error
and form
. The first reflects actual validity state according to states of registered inputs. The second contains error message (can be set using API). The last one is instance of the component and provides access to the API.
With InputGroup
we define a fieldset that contains one or more semantically related inputs (e.g. selects day
, month
, year
usually go togather). The group expose valid
, error
, errors
and inputGroup
variables on children Nodes. So we can automatically toggle group error message and content classList
depending on them.
We register inputs within InputGroup
with validate
prop. There we can specify a validator function, which will extend HTMP5 form validation logic. Very easily we can provide mapping for standard validation message (translate
prop).
npm i react-html5-form
You can see the component in action at LIVE DEMO 📺
- source code of example with HTML elements for inputs (styled with Bootstrap 4)
- source code of example with read-made components for inputs (styled with MaterialUI.Next)
- source code of example with connection to Redux
Represents form
import { Form } from "react-html5-form";
<Function>
onSubmit
- (OPTIONAL) form submit handler<Function>
onMount
- (OPTIONAL) callback that gets invoked incomponentDidMount()
, it receives component instance as parameter<Function>
onUpdate
- (OPTIONAL) callback that gets invoked when form ready (all input groups registered) and when it changes validity state. It's intended to be used with Redux to update tree state- any attributes of Form HTML element
scrollIntoViewFirstInvalidInputGroup()
- scroll first input group in invalid state into view (happens automatically onSubmit)checkValidityAndUpdateInputGroups()
- check form validity and update every input groupcheckValidityAndUpdate()
- check form validity and update only form state (without updating input groups)setError( message = "")
- set form scope error messagesubmit()
- submit formgetRef()
- getRef
instance pointed to the generatedFORM
element. Thus if you need to access the DOM node, you go withform.getRef().current
debugInputGroups( inx = null )
- get debug info about all registered inputs, or one matching given index
<String>
error
- error message set withsetError()
<Boolean>
valid
- form validity state<Boolean>
pristine
- true if user has not interacted with the form yet.<Boolean>
submitting
- true while form is being processed on submission<Boolean>
submitted
- true after form submitted<React.Component>
form
- link to the component API
State properties
valid
,pristine
,submitting
,submitted
are also available as element data-attributes e.g.data-submitting="false"
import React from "react";
import { render } from "react-dom";
import { Form } from "react-html5-form";
const MyForm = props => (
<Form id="myform">
{({ error, valid }) => (
<>
Form content
</>
)}
</Form>
);
render( <MyForm />, document.getElementById( "app" ) );
<Form>
creates <form noValidate ..>
wrapper for passed in children. It delegates properties to the produced HTML Element.
For example specified id
ends up in the generated form.
async function onSubmit( form ) {
form.setError("Opps, a server error");
return Promise.resolve();
};
const MyForm = props => (
<Form onSubmit={onSubmit} id="myform">
{({ error, valid, pristine, submitting, form }) => (
<>
{ error && (<div className="alert alert-danger" role="alert">
<strong>Oh snap!</strong> {error}
</div>)
}
Form content
<button disabled={ ( pristine || submitting ) } type="submit">Submit</button>
</>
)}
</Form>
);
The API can be accessed by form
reference. Besides, any handler passed through onSubmit
prop gets wrapped.
The component calls setState
for checkValidity
prior calling the specified handler. From the handler you can
access the API by reference like form.setError
. What is more, we disable submit button until the user first interaction with the form and on form submission (onSubmit is an asynchronous function / Promise; as it resolves the state property
submitting` set in false).
InputGroup
defines scope and API for a group of arbitrary inputs registered with validate
prop. InputGroup
exposes in the scope parameters that can be used to toggle group validation message(s), group state representation and to access the component API
import { Form, InputGroup } from "react-html5-form";
- <Object|Array>
validate
- (REQUIRED) register inputs to the group. Accepts either array of input names (to match[name=*]
) or plain object out of pairsname: custom validator
// Group of a single input
<InputGroup validate={[ "email" ]} />
// Group of multiple inputs
<InputGroup validate={[ "month", "year" ]} />
// Custom validator
<InputGroup validate={{
"vatId": ( input ) => {
if ( !input.current.value.startsWith( "DE" ) ) {
input.setCustomValidity( "Code must start with DE" );
return false;
}
return true;
}
}}>
-
translate
- (OPTIONAL) set custom messages for validation constraints (see ValidityState Properties)<InputGroup ... translate={{ firstName: { valueMissing: "C'mon! We need some value", patternMismatch: "Please enter a valid first name." } }}>
-
tag
- (OPTIONAL) define tag for the generated container HTML element (by defaultdiv
)
<InputGroup tag="fieldset" ... />
<Function>
onMount
- (OPTIONAL) callback that gets invoked incomponentDidMount()
, it receives component instance as parameter<Function>
onUpdate
- (OPTIONAL) callback that gets invoked when form ready (all input groups registered) and when it changes validity state. It's intended to be used with Redux to update tree state- any attributes of HTML element
checkValidityAndUpdate()
- runcheckValidity()
and update the input group according to the actual validity stategetInputByName( name )
- access input fo the group by given input namegetValidationMessages()
- get list of all validation messages within the groupcheckValidity()
- find out if the input group has not inputs in invalid stategetRef()
- getRef
instance pointed to the generated element. Thus if you need to access the DOM node, you go withinputGroup.getRef().current
<String>
error
- validation message for the first invalid input<String[]>
errors
- array of validation messages for all the inputs<Boolean>
valid
- input group validity state (product of input states)<Boolean>
pristine
- true if user has not interacted with the form yet.<React.Component>
inputGroup
- link to the component API
<InputGroup tag="fieldset" validate={[ "email" ]} translate={{ email: { valueMissing: "C'mon! We need some value" } }}> {({ error, valid }) => ( <div className="form-group"> <label htmlFor="emailInput">Email address</label> <input type="email" required name="email" className={`form-control ${!valid && "is-invalid"}`} id="emailInput" placeholder="Enter email" /> { error && (<div className="invalid-feedback">{error}</div>) } </div> )} </InputGroup>
Here we define an input group with registered input matching
[name=email]
. We also specify a custom message forValidityState.valueMissing
constraint. It means that during the validation the input has no value while having constraint attributerequired
the input group receive validation message"C'mon! We need some value"
in scope parametererror
.<InputGroup validate={{ "vatId": ( input ) => { if ( !input.current.value.startsWith( "DE" ) ) { input.setCustomValidity( "Code must start with DE." ); return false; } return true; } }}> {({ error, valid }) => ( <div className="form-group"> <label htmlFor="vatIdInput">VAT Number (optional)</label> <input className={`form-control ${!valid && "is-invalid"}`} id="vatIdInput" name="vatId" placeholder="Enter VAT Number"/> { error && (<div className="invalid-feedback">{error}</div>) } </div> )} </InputGroup>
Here we define a custom validator for
vatId
input (viavalidate
prop). The validator checks if the current input value starts with"DE"
. If it dosn't we applysetCustomValidity
method to set the input (and the group) in invalid state (the same as of HTML5 Constraint validation API).const validateDateTime = ( input ) => { if ( input.current.value === "Choose..." ) { input.setCustomValidity( `Please select ${input.current.title}` ); return false; } return true; };
<InputGroup validate={{ "day": validateDateTime, "month": validateDateTime }}> {({ errors, valid }) => ( <div className="form-group"> <div className="form-row"> <div className="form-group col-md-6"> <label htmlFor="selectDay">Day</label> <select name="day" id="selectDay" title="Day" className={`form-control ${!valid && "is-invalid"}`}> <option>Choose...</option> <option>...</option> </select> </div> <div className="form-group col-md-6"> <label htmlFor="selectMonth">Month</label> <select name="month" id="selectMonth" title="Month" className={`form-control ${!valid && "is-invalid"}`}> <option>Choose...</option> <option>...</option> </select> </div> </div> { errors.map( ( error, key ) => ( <div key={key} className="alert alert-danger">{error}</div> )) } </div> )} </InputGroup>
On validation we receives
errors
array that contains validation messages for all the inputs registered in the group. So we can display the messages within the group container.Let's first define the
onInput
event handler:const onInput = ( e, inputGroup ) => { inputGroup.checkValidityAndUpdate(); };
We use
checkValidityAndUpdate
method to actualize validity state and update the component.Now we subscribe the component:
<InputGroup validate={[ "firstName" ]} translate={{ firstName: { patternMismatch: "Please enter a valid first name." } }}> {({ error, valid, inputGroup }) => ( <div className="form-group"> <label htmlFor="firstNameInput">First Name</label> <input pattern="^.{5,30}$" required className={`form-control ${!valid && "is-invalid"}`} id="firstNameInput" name="firstName" onInput={( e ) => onInput( e, inputGroup, form ) } placeholder="Enter first name"/> { error && (<div className="invalid-feedback">{error}</div>) } </div> )} </InputGroup>
Here while assigning event handler inline we delegate the instance of component to earlier created
onInput
function likeonInput={( e ) => onInput( e, inputGroup ) }
. Thus we can operate it when responding to the event.Represents input element within input group scope.
current
- input HTML elementsetCustomValidity( message = "" )
- set the input in invalid statecheckValidity()
- returns boolean truthy when all the input in valid stategetValidationMessage()
- get validation message considering assigned custom message if any
The package exposes reducer
html5form
, so one can connect form/input group/input states to the store:import React from "react"; import { render } from "react-dom"; import { createStore, combineReducers } from "redux"; import { Provider } from "react-redux"; import { App } from "./Containers/App.jsx"; import { html5form } from "react-html5-form"; const appReducer = combineReducers({ html5form }); // Store creation const store = createStore( appReducer ); render( <Provider store={store}> <App /> </Provider>, document.getElementById( "app" ) );
Thus we get package-specific state tree in the store:
- See full example here
-