@react-declarative-form/core is the core of a simple-to-use declarative valadation library. It is designed to make building forms easy; allowing consumers to specify validator requirements on individual form components, and let the library do the heavy lifting.
This library requires the following:
- The
<Form />
component from @react-declarative-form/core is used instead of a html<form/>
. - Managed form components have been created using the
bind() HOC
. See Example: Form Binding HOC. - The managed form components are descendants of a
<Form />
component. See Example: Form Usage.
- Add @react-declarative-form/core to your project.
yarn add @react-declarative-form/core
- Create bound form components using
bind() HOC
- Use bound form components within a
<Form />
component.
Each bound component registers itself with the closest <Form />
ancestor which allows the form to manage the value, pristine & validator state. This is done using the React 16.3 context API. While the form manages the component state, each component is updated individually on an as needed basis to minimise unnecessary renders. If a components value is changed, then only that component will be re-rendered, assuming it is not part of a validator trigger. However, if the component does belong to a validator trigger, then the related components will also be validated and re-rendered. When the setValue function is called, the component is validated using the new value.
- Overview
- Getting started
- How does it work?
- Table of Contents
- Documentation
- Authors
<Form>
is a component that is to be used in place of a regular HTML form component. It supports event handlers as props, and programmatic control (accessed via ref).
Name | Type | Required | Description |
---|---|---|---|
onChange |
(componentName: string, value: any) => void | false | Called when the value of a bound form component has been changed. The new value is provided. |
onBlur |
(componentName: string, value: any) => void | false | Called when a bound form component has been blurred. The current value is provided. |
onFocus |
(componentName: string, value: any) => void | false | Called when a bound form component has been focused. The current value is provided. |
onSubmit |
(values: ValueMap) => void | false | Called when the form is programmatically submitted, or a button with type="submit" is clicked. The current values for all bound form components are provided. |
onValidSubmit |
(values: ValueMap) => void | false | Called after onSubmit if all bound form components are valid. The current values for all bound form components are provided. |
onInvalidSubmit |
(values: ValueMap) => void | false | Called after onSubmit at least 1 bound form component is invalid. The current values for all bound form components are provided. |
withHiddenSubmit |
boolean | false | Whether a hidden submit button should be included in the form. |
sticky |
boolean | false | Whether the form component values should be sticky and retain their value in between component unmounts and mounts. |
initialValues |
ValueMap | false | Initial values to be provided to the bound form components. This is useful for populating the form without having to manage all form values. |
submit(): { isValid: boolean, values: ValueMap }
Programatically submit the form. If you don't want to manually call this, a button with type submit should be provided to the form. This can be provided in your form implementation, or automatically using the withHiddenSubmit
prop on the Form.
Returns: an object with 2 properties:
isValid
: whether the entire form was valid when submittingvalues
: all of the form values at the time of submission
clear(componentName?: string | string[]): Promise<void>
Clears the specified component(s) by setting their value to null. If no component names are provided, all components within the form will be cleared.
Params:
componentName
: component name(s) to be cleared
Returns: a promise which is resolved once the react components have been re-rendered
reset(componentName?: string | string[]): Promise<void>
Resets the specified component(s) by unsetting their value, validator and pristine state. If no component names are provided, all components within the form will be reset.
Params:
componentName
: component name(s) to be reset
Returns: a promise which is resolved once the react components have been re-rendered
validate(componentName?: string | string[]): Promise<void>
Validates specified component(s) by executing the validator and updating the components to reflect their validator state. If no component names are provided, all components within the form will be validated.
Params:
componentName
: component name(s) to be reset
Returns: a promise which is resolved once the react components have been re-rendered.
isValid(componentName?: string | string[]): boolean
Determines if all the specified component(s) are valid by executing the validator using the components current value. If no component names are provided, all components within the form will be tested.
Params:
componentName
: component name(s) to be tested
Returns: a boolean flag to indicate whether all the components are valid
Note: if validatorData is being managed, the provided validatorData.context will be used instead of executing the validator.
isPristine(componentName?: string | string[]): boolean
Determines if all the specified component(s) are pristine - the component has not been modified by the user or by programatically calling setValue. If no component names are provided, all components within the form will checked.
Params:
componentName
: component name(s) to be tested
Returns: a boolean flag to indicate whether all the components are pristine
getValidatorData(componentName: string): ValidatorData
Returns the components current validatorData. There are 2 ways a components validator data can be retrieved (in order of precedence):
- externally managed validatorData prop provided to the component
- internally managed validatorData state when the user changes input
Params:
componentName
: name of the component to get validator data for
Returns: component validator data if exists, otherwise an object with undefined context and message will be returned.
getValue(componentName: string): any
Returns the value of the specified component. There are four ways a component value can be provied (in order of precedence):
- externally managed value prop provided to the component
- internally managed state value when the user changes input
- initialValues provided to the form component
- defaultValue specified on individual form component
Params:
componentName
: name of the component to get value for
Returns: component value
Note: the form values should not be mutated
getValues(componentNames?: string[]): ValueMap
Gets the values of the provided component names using the same logic as getValue
.
Params:
componentNames
: component names to retrieve values for
Returns: an object with componentName:value pairs
setValidatorData(componentName: string, data: ValidatorData): Promise<void>
Sets the component internally managed validatorData & updates the component to reflect its new state.
Params:
componentName
: name of the component which should be updatedvalidatorData
: the new validator data to be stored in Form state
Returns: a promise which is resolved once the react component has been re-rendered.
setValue(componentName: string, value: any, pristine: boolean = false): Promise<void>
Sets the component internally managed state value & updates the component validatorData using the provided value. By default, the components pristine state will be set to false
to indicate that the component has been modified.
Params:
componentName
: name of the component to set value forvalue
: the new value to be stored in Form statepristine
: the new pristine state when setting this value (default: false).
Returns: a promise which is resolved once the react component has been re-rendered.
setValues(values: ValueMap, pristine?: boolean): Promise<void>
Sets the components internally managed state values & updates their component validatorData using the provided values. By default, the components pristine state will be set to false
to indicate that the components have been modified.
Params:
values
: the values to be saved in Form state (componentName:value map)
Returns: a promise which is resolved once the react components have been re-rendered.
Using the bind()
higher order component allows @react-declarative-form/core to manage the form component value and validator state. Refer to Example: Form Binding HOC to get a better understanding of how these props can be used.
These props will be available to the bound component. The injected variables will always be provided to the bound component, even if the the the consumer did not provide them. However, a number of these props are overridable, meaning the consumer can override the HOC provided value.
Name | Type | Required | Injected | Overridable | Description |
---|---|---|---|---|---|
name |
string | true | false | true | Unique form component identifier |
required |
boolean | false | false | true | Whether or not a value is required |
pristine |
boolean | false | true | true | Whether or not the value has been modified |
validatorData |
ValidatorData | false | true | true | Data which reflects the current validator state for the component. |
onBlur |
React.EventHandler | false | true | true | Should be called when component has been blurred |
onFocus |
React.EventHandler | false | true | true | Should be called when component has been focused |
value |
any | false | true | true | Current form component value |
setValue |
(value: any) => void | - | true | false | Should be called when component value has changed |
Notes
ValidatorData
is an object:{ message: string, context: ValidatorContext }
.ValidatorContext
is an enum which expects strings:danger
,warning
&success
.setValue
can not be overridden, providing it will have no effect.
These props are only used by the HOC and are not passed to the wrapped component.
Name | Type | Required | Description |
---|---|---|---|
validatorRules |
ValidatorRules | false | Validation rules which should be applied to the component |
validatorMessages |
any | false | Custom validator messages for specific validator rules |
validatorTrigger |
string | false | Triggers validator to execute on the specified component names when this component is modified |
defaultValue |
any | false | Default value to be applied if the component does not have a managed, state or initial value |
clear(): Promise<void>
Clears the scomponent by setting its value to null.
Returns: a promise which is resolved once the react component has been re-rendered
reset(): Promise<void>
Resets the component by unsetting its value, validator and pristine state.
Returns: a promise which is resolved once the react component has been re-rendered
validate(): Promise<void>
Validates the component by executing the validator and updating the component to reflect its new validator state. If no component names are provided,
Returns: a promise which is resolved once the react component has been re-rendered
isValid(): boolean
Determines if the component is valid by executing the validator using the components current value.
Returns: a boolean flag to indicate whether the component is valid
isPristine(): boolean
Determines if the component is pristine - the component has not been modified by the user or by programatically calling setValue.
Returns: a boolean flag to indicate whether the component is pristine
getValidatorData(): ValidatorData
Returns the components current validatorData. There are 2 ways a components validator data can be retrieved (in order of precedence):
- externally managed validatorData prop provided to the component
- internally managed validatorData state when the user changes input
Returns: component validator data
Note: If the component has no validatorData, then an object with undefined context & message will be returned.
getValue(): any
Returns the value of the component. There are four ways a component value can be provied (in order of precedence):
- externally managed value prop provided to the component
- internally managed state value when the user changes input
- initialValues provided to the form component
- defaultValue specified on individual form component
Returns: component value
Note: the form values should not be mutated
setValidatorData(data: ValidatorData): Promise<void>
Sets the component internally managed validatorData & updates the component to reflect its new state.
Params:
validatorData
the new validator data to be stored in Form state
Returns: a promise which is resolved once the react component has been re-rendered.
setValue(value: any, pristine?: boolean): Promise<void>
Sets the component internally managed state value & updates the component validatorData using the provided value. By default, the components pristine state will be set to false
to indicate that the component has been modified.
Params:
value
the new value to be stored in Form statepristine
the new pristine state when setting this value (default: false).
Returns: a promise which is resolved once the react component has been re-rendered.
Validator rules are executed sequentially (in the order in which they are defined) until validator data has been returned, or all rules have been executed. If no rule has returned validator data, then a data object with Success context will be returned.
The following validator rules are built-in. By default, they will only return ValidatorContext.Danger if the a value is defined and it fails to pass the test. However, this behaviour can be customized by overriding built-in rules. See the Adding additional validator rules section for more information. Additional built-in validator rules will be added in the future.
Name | Criteria | Description |
---|---|---|
required |
boolean | Whether the input is required |
equals |
number | string | Input is === to the specified value |
notEquals |
number | string | Input is !== to the specified value |
greaterThan |
number | Input is > than the specified value |
lessThan |
number | Input is < than the specified value |
minValue |
number | Input is >= to the specified value |
maxValue |
number | Input is <= to the specified value |
isDivisibleBy |
number | Input is divisible by the specified number |
isInteger |
boolean | Input is an integer |
isDecimal |
boolean | Input is a decimal number |
isNumeric |
boolean | Input is numeric characters only [0-9]+ |
minLength |
number | Input length is at least the specified length |
maxLength |
number | Input length is at most the specified length |
isLength |
number | Input length equals the specified length |
isLowercase |
boolean | Input is all lowercase characters |
isUppercase |
boolean | Input is all uppercase characters |
matches |
RegExp | Input matches the specified regex pattern |
isEmail |
boolean | Input is a valid email address |
isUrl |
boolean | Input is a valid url |
isCreditCard |
boolean | Input is a valid credit card number |
isHexColor |
boolean | Input is a valid hexadecimal color |
isIp |
boolean | Input is a valid IPv4 or IPv6 address |
isPort |
boolean | Input is a valid port number |
eqTarget |
string | Input value is == to target input value |
gtTarget |
string | Input value is > to target input value |
gteTarget |
string | Input value is >= to target input value |
ltTarget |
string | Input value is < to target input value |
lteTarget |
string | Input value is <= to target input value |
custom |
ValidatorRule | Custom validator rule. It is executed before other rules |
Note: using the custom key allows consumers to define a custom validator rule. This is useful when one-off custom validator logic is required. Ideally, validator rules should be designed for reusability.
Adding additional validator rules can be done using the addValidatorRule function. An example can be found in Example: Adding validator rules.
addValidatorRule: (key: string, rule: ValidatorRule) => void
Note: If a rule with the same key exists already, it will be overridden - this includes built-in rules.
enum ValidatorContext {
Danger = 'danger',
Warning = 'warning',
Success = 'success',
}
type ValidatorRule = (
key: string,
values: ValueMap,
criteria?: any,
) => ValidatorData;
interface ValidatorRuleMap {
readonly [name: string]: ValidatorRule;
}
interface ValidatorData {
readonly name?: string;
readonly context: ValidatorContext;
readonly message?: string;
}
interface ValueMap {
[name: string]: any;
}
// validatorConfig.ts
import {
addValidatorRule,
isDefined,
ValidatorContext,
ValueMap,
} from '@react-declarative-form/core';
addValidatorRule(
'containsCat',
(componentName: string, values: ValueMap, criteria: boolean) => {
const value = values[componentName];
const pattern = /\bcat\b/i;
if (isDefined(value) && !pattern.test(value)) {
return {
context: ValidatorContext.Danger,
message: 'Needs more Cat.',
};
}
},
);
Using the bind HOC with the TextField component provided by material-ui
import * as React from 'react';
import {
bind,
BoundComponentProps,
ValidatorContext,
} from '@react-declarative-form/core';
import {
TextField as MaterialTextField
TextFieldProps as MaterialTextFieldProps
} from '@material-ui/core';
export interface TextFieldProps
extends MaterialTextFieldProps, BoundComponentProps
{
name: string;
label?: string;
}
export class UnboundTextField extends React.Component<TextFieldProps> {
public render() {
const {
name,
value,
setValue,
onChange,
validatorData,
pristine,
...restProps
} = this.props;
const hasError = validatorData.context === ValidatorContext.Danger;
return (
<MaterialTextField
{...restProps}
id={name}
name={name}
value={value || ''}
onChange={this.handleChange}
error={!pristine && hasError}
helperText={!pristine && validatorData.message}
/>
);
}
private handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { onChange, setValue } = this.props;
setValue(event.currentTarget.value);
if (onChange) onChange(event);
};
}
export const TextField = bind<TextFieldProps>(UnboundTextField);
Using the bound TextField component inside a Form.
import * as React from 'react';
import { Form, ValueMap } from '@react-declarative-form/core';
import { Button, TextField } from 'view/components';
export interface RegistrationFormProps {}
export class RegistrationForm extends React.Component<RegistrationFormProps> {
public render() {
return (
<Form onValidSubmit={this.handleValidSubmit}>
<TextField
name="email"
label="Email"
validatorRules={{
isEmail: true,
}}
required
/>
<TextField
name="password"
label="Password"
validatorTrigger={['password-confirm']}
validatorRules={{
minLength: 8,
}}
type="password"
required
/>
<TextField
name="password-confirm"
label="Confirm password"
validatorRules={{
eqTarget: 'password',
}}
validatorMessages={{
eqTarget: 'Must match password',
}}
type="password"
required
/>
<TextField
name="favourite-animal"
label="Favourite animal"
validatorRules={{
// Custom validator rule, see: "Adding custom validator rules"
containsCat: true,
}}
required
/>
<Button title="Submit" type="submit" />
</Form>
);
}
private handleValidSubmit = (values: ValueMap) => {
console.log('Successfully submitted form :)', values);
};
}
- Peter Weller @peterweller_