Skip to content

Latest commit

 

History

History
567 lines (439 loc) · 27.2 KB

README.md

File metadata and controls

567 lines (439 loc) · 27.2 KB

@react-declarative-form/core

Overview

@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.

Requirements

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.

Getting started

  1. Add @react-declarative-form/core to your project.
    yarn add @react-declarative-form/core
    
  2. Create bound form components using bind() HOC
  3. Use bound form components within a <Form /> component.

How does it work?

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.

Table of Contents

Documentation

Form component

<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).

Form Props

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.

Form API

Func: submit

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 submitting
  • values: all of the form values at the time of submission
Func: clear

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

Func: reset

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

Func: validate

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.

Func: isValid

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.

Func: isPristine

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

Func: getValidatorData

getValidatorData(componentName: string): ValidatorData

Returns the components current validatorData. There are 2 ways a components validator data can be retrieved (in order of precedence):

  1. externally managed validatorData prop provided to the component
  2. 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.

Func: getValue

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):

  1. externally managed value prop provided to the component
  2. internally managed state value when the user changes input
  3. initialValues provided to the form component
  4. 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

Func: getValues

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

Func: setValidatorData

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 updated
  • 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.

Func: setValue

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 for
  • value: the new value to be stored in Form state
  • pristine: the new pristine state when setting this value (default: false).

Returns: a promise which is resolved once the react component has been re-rendered.

Func: setValues

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.

bind HOC

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.

Bound component props

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

  1. ValidatorData is an object: { message: string, context: ValidatorContext }.
  2. ValidatorContext is an enum which expects strings: danger, warning & success.
  3. setValue can not be overridden, providing it will have no effect.

Higher order component props

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

bind HOC API

Func: clear

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

Func: reset

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

Func: validate

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

Func: isValid

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

Func: isPristine

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

Func: getValidatorData

getValidatorData(): ValidatorData

Returns the components current validatorData. There are 2 ways a components validator data can be retrieved (in order of precedence):

  1. externally managed validatorData prop provided to the component
  2. 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.

Func: getValue

getValue(): any

Returns the value of the component. There are four ways a component value can be provied (in order of precedence):

  1. externally managed value prop provided to the component
  2. internally managed state value when the user changes input
  3. initialValues provided to the form component
  4. defaultValue specified on individual form component

Returns: component value

Note: the form values should not be mutated

Func: setValidatorData

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.

Func: setValue

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 state
  • pristine 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

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.

Built-in validator rules

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

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.

Types

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;
}

Examples

Example: Adding validator rules

// 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.',
            };
        }
    },
);

Example: Form Binding HOC

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);

Example: Form Usage

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);
    };
}

Authors