There are several scenarios where a parent component's validation depends on
whether or not its descendant elements validate. By using the React context
api, manual crawling of the React render tree can be
avoided. Instead, a handler function in the context can be called to update the
parent's state whenever the descendant updates. This simplifies the
implementation of validation in the parent component.
This package is available on npm. Install it using:
npm install --save react-validation-contextThis library revolves around the idea of "validity". A component can have one of the following validities:
@typedef {(undefined|null|Boolean)} Validityundefined- No validation state defined. This is the default.null- Validation is disabled.true- Validation passed.false- Validation failed.
It is useful to know when a component's validity changes. As such, this library attempts to provide a uniform API for validation change handlers. In general, a validity change handler has the following API:
{Function} onValidChange- Validity change handler.@param {String} name- The unique identifier for the component whose validity changed.@param {Validity} isValid- The current validity of the component.@param {Validity} wasValid- The previous validity of the component.
The name identifier is also key to this library. It allows a collection of
descendants to be validated by a parent component. Essentially, this library
provides a way of tracking the validities of these various names. Note that this
is not the same as tracking the validities of the components themselves,
since a component's name may change as well!
The Validates component is used to wrap a component that can be validated,
providing the logic for validation change handlers.
{String} name- A unique identifier for the component. Required.{Validity} validates- The component's validity.{Function} onValidChange- Validity change handler.{ReactElement} children- Children. The component only accepts a single child, and will simply render as that child.
The function passed as the onValidChange prop will be called when:
- The component mounts and the
validatesprop is notundefined - The component unmounts and the
validatesprop is notundefined - The component's
validatesprop changes
During these cases, the onValidChange handler is called with:
- The component's
nameprop - The component's validity
- The component's previous validity
However, if the component's name changes and the validates prop is not
undefined, then the onValidChange handler will first be called with:
- The previous
nameprop undefined, to indicate that the previousnameno longer has validation defined- The component's previous validity, if applicable
Then, if the validates prop has changed, the onValidChange handler is called
a second time.
If onValidChange is present in Validate's context, it will call that context
handler whenever the onValidChange handler in the props would be called, as
described above.
importReactfrom'react';import{string,func}from'prop-types';import{Validates}from'react-validation-context';/** * An input that only validates when its value is non-empty and non-whitespace. */exportdefaultclassRequiredInputextendsReact.Component{constructor(props){super();// Set up the initial state based on whether the initial value validatesconst{ value, defaultValue }=props;constvalidates=this.validate(value||defaultValue);this.state={ validates };this.onChange=this.onChange.bind(this);}// Check that the value exists and is non-whitespacevalidate(value){returnvalue&&value.trim().length>0;}// Wrap the onChange handler to update `this.state.validates`onChange(e){const{ onChange }=this.props;if(onChange){onChange(e);}constvalidates=this.validate(e.target.value);this.setState({ validates });}render(){const{ onChange }=this;const{ onValidChange, name, ...inputProps}=this.props;const{ validates }=this.state;// Set up `input` propsObject.assign(inputProps,{ onChange, name });// Render `input` and validation context-aware `Validates`return<Validatesvalidates={validates}onValidChange={onValidChange}name={name}><inputtype='text'{ ...inputProps}/></Validates>;}}RequiredInput.propTypes={name: string.isRequired,// Input identifier namevalue: string,// Input valuedefaultValue: string,// Default input valueonChange: func,// onChange handler for inputonValidChange: func// validity change handler};The Validate component is used to wrap a component which has descendants that
may be validated, and provides an interface for validating all of those
descendants. It extends Validates to provide the same interface for listening
for validation changes on the component itself.
NOTE: This component is able to keep track of all conforming descendant
components (not just direct children) via the React context api.
{Function} validate- Validation function for descendants.@param {Object} valids- The keys are the descendantnames, and the values are their{Validity}s.@returns {Validity}- The validity of this component.
This component also inherits all the props of Validate.
The function passed as the validate prop will be called whenever the component
must validate its descendants. This can occur when the component first mounts,
or when at least one of its descendants has changed its name or validity.
Whenever the validate function is called, it is given a single argument: an
Object whose keys are the names of the descendant components, and whose
values are their validities. The validate function should then return the
validity of the component.
If onValidChange is present in Validate's context, it will call that context
handler appropriately.
importReactfrom'react';import{string,func,node}from'prop-types';import{Validate}from'react-validation-context';importstylesfrom'./form.less';exportdefaultclassFormextendsReact.Component{constructor(){super();// The form is initially validthis.state={validates: true};this.onValidChange=this.onValidChange.bind(this);this.onSubmit=this.onSubmit.bind(this);}// Wrap the onValidChange handler to set this.state.validatesonValidChange(name,isValid,wasValid){const{ onValidChange }=this.props;if(onValidChange){onValidChange(name,isValid,wasValid);}this.setState({validates: isValid});}// Wrap the onSubmit handler to prevent submission if the form is invalidonSubmit(e){const{ onSubmit }=this.props;if(onSubmit){onSubmit(e);}if(!this.state.validates){e.preventDefault();}}// The form is invalid if there are any invalid items in its validation contextvalidate(valids){returnObject.keys(valids).every(k=>valids[k]!==false);}render(){const{ onSubmit, onValidChange, validate }=this;const{ children, name, ...formProps}=this.props;deleteformProps.onValidChange;// avoid passing down `onValidChange` from props// Set up `form` propsObject.assign(formProps,{ onSubmit, name });// Render `form` and create validation context `Validate` (which is also validation context-aware)return<Validatevalidate={validate}onValidChange={onValidChange}name={name}><form{ ...formProps}>{children}</form></Validate>;}}Form.propTypes={name: string.isRequired,// Form identifier nameonSubmit: func,// onSubmit handler for formonValidChange: func,// validity change handlerchildren: node// React children};For a more in-depth demonstration, see the example project under demo/.