1.4.3 • Published 6 years ago

react-formctrl v1.4.3

Weekly downloads
1
License
MIT
Repository
github
Last release
6 years ago

Build Status Coverage Status Known Vulnerabilities

React Form CTRL

A declarative form controller and validator for ReactJS.

Bundle size: 6.9 KB gzipped

Features

  • No schema
  • Declarative
  • Extremely reusable forms
  • Field level reusability
  • Built-in and custom validators
  • Controlled inputs
  • With decorators

1. Getting started

npm install --save react-formctrl

1.1. Wrap all your application with:

export function App(props) {
    return (
        <FormProvider>
            {'... your app ...'}
        </FormProvider>
    )
}

1.2. Create and decorate your field component:

let InputField = ({label, placeholder, name, type, required, onChange, onBlur, value}) => {

    const getLabel = () => {
        return required ? `${label}*` : label
    }

    return (
        <div>
            <label for={name}>{getLabel()}</label>
            <input id={name} name={name} 
                type={type} 
                onChange={onChange} 
                onBlur={onBlur}
                placeholder={placeholder || label}
                value={value} 
            />
        </div>
    );
}
InputField = controlledField()(InputField)

Now, your field component will need two required props:

  • form: the name of the form that the field is attached to;
  • name: the name of the field;

And will have some optional properties too:

  • type: The input field type.
  • required: true if the input field is required.
  • pattern: The regex to validate the field pattern.
  • integer: true if when the Field type property is "number" and should validate to integer value.
  • match: Another field name that the value of this field should match.
  • min: The min number value of a field with type "number".
  • max: The max number value of a field with type "number".
  • minLength: The min string value length of a field.
  • maxLength: The max string value length of a field.

The controlledField decorator will inject a ctrl property which can be used to access the field state:

  • valid/invalid: The field validation state;
  • pristine/dirty: The field modification state;
  • untouched/touched: The field access state (changed on blur);
  • unchanged/changed: The field change state (initial value comparison);
  • errors: An array of the validation errors: ([{key: 'email', params: {value: 'email@'}}]);

The controlledField decorator automatically handles the value, onChange and onBlur properties, so you just need to bind them to a input.

1.3. Build and decorate your forms:

let PersonForm = ({form, formCtrl, onSubmit, person = {}}) => (
    <Form name={form} onSubmit={onSubmit}>
        <div class="fieldset">
            <div class="fields-container">
                <InputField 
                    form={form} 
                    name="name" 
                    label="Name" 
                    initialValue={person.name} 
                    required 
                />
                <InputField 
                    form={form} 
                    name="email" 
                    type="email" 
                    label="E-mail" 
                    initialValue={person.email} 
                    required 
                />
            </div>
            <div class="buttons-container">
                <button type="submit" disabled={formCtrl.invalid || formCtrl.unchanged}>Save</button>
                <button type="reset" disabled={formCtrl.unchanged}>Reset</button>
            </div>
        </div>
    </Form>
)
PersonForm = controlledForm()(PersonForm)

Now, your field component will need a required props:

  • form: the name of the form that the controller will be attached to;

The controlledForm decorator will inject a formCtrl property which can be used to access the form state:

  • valid/invalid: The form's fields validation state;
  • pristine/dirty: The form's fields modification state;
  • untouched/touched: The form's fields access state (changed on blur);
  • unchanged/changed: The form's fields change state (initial value comparison);
  • values: The values of the form ({[fieldName]: 'fieldValue'});
  • files: the selected files of the form ({[fieldName]: File[]});

If you need to programatically change a field's value, use: formCtrl.setFieldValue('fieldName', 'newValue'). Just be careful about the phase that you trigger the change, because this will trigger the Form and related Field update phase. So, ensure the form component update effects don't triggers setFieldValue again infinitely.

1.4. Finally, use and reuse your forms!

function CreatePersonRoute() {
    return <PersonForm form="createPersonForm" />
}

function EditPersonRoute() {
    const person = {
        name: 'Leandro Hinckel Silveira',
        email: 'leandro.hinckel@gmail.com'
    }
    return <PersonForm form="editPersonForm" person={person} />
}

2. Adding custom validation

2.1. Create a class that extends CustomValidator

class NoStupidPassword extends CustomValidator {
    constructor() {
        super('stupidpass')
    }
    validate(formCtrl, props, value, files) {
        return !/^123456789$/i.test(value)
    }
}

The string parameter of super constructor determines the key name of the validator to use it later.

2.2 Declare it on FormProvider component

function App() {
    return (
        <FormProvider validators={[new NoStupidPassword()]}>
            <UserForm form="userForm">
        </FormProvider>
    )
}

2.3 Then activate the validator

Use the validator's key name passed to the super constructor to activate the validation:

let UserForm = ({form, formCtrlm onSubmit}) => (
    <Form name={form} onSubmit={onSubmit}>
        <InputField 
            form={form} 
            name="username" 
            label="Username" 
            required 
        />
        <InputField 
            form={form} 
            name="password" 
            type="password" 
            label="Password" 
            validate="stupidpass"
            required 
        />
        <InputField 
            form={form} 
            name="confirmPassword"
            type="password"
            label="Confirm password"
            match="password"
            validate="stupidpass"
            required 
        />
        <button type="submit" disabled={formCtrl.invalid || formCtrl.unchanged}>Save</button>
    </Form>
)
UserForm = controlledForm()(UserForm)

3. Reach field level reusability

The field level reusability means that even the specific forms fields can be reusable thanks to Form and Field decoupling.

3.1 Create form's part components

function UserInformationsFields({form, user = {}}) {
    return (
        <div>
            <InputField 
                form={form} 
                name="name" 
                label="Full name" 
                initialValue={user.name} 
            />
            <InputField 
                form={form} 
                name="email" 
                type="email" 
                label="E-mail" 
                initialValue={user.email} 
            />
            <InputField 
                form={form} 
                name="confirmEmail" 
                type="email" 
                label="Confirm e-mail" 
                initialValue={user.email} 
                match="email"
            />
        </div>
    )
}
function UserCredentialsFields({form, user = {}}) {
    return (
        <div>
            <InputField 
                form={form} 
                name="username" 
                label="Username" 
                initialValue={user.username} 
                required
                minLength={6}
                maxLength={18}
            />
            <InputField 
                form={form} 
                name="password" 
                type="password" 
                label="Password" 
            />
            <InputField 
                form={form} 
                name="confirmPassword" 
                type="password" 
                label="Confirm password" 
                match="password"
            />
        </div>
    )
} 

3.2 Reuse them in different forms

let QuickUserRegistrationForm = ({form, formCtrl, onSubmit}) => (
    <Form name={form} onSubmit={onSubmit}>
        <UserCredentialsFields form={form} />
        <button type="submit" disabled={formCtrl.invalid || formCtrl.unchanged}>Save</button>
    </Form>
)
let FullUserForm = ({form, formCtrl, onSubmit, user = {}}) => (
    <Form name={form} onSubmit={onSubmit}>
        <UserInformationsFields form={form} user={user} />
        <UserCredentialsFields form={form} user={user} />
        <button type="submit" disabled={formCtrl.invalid || formCtrl.unchanged}>Save</button>
    </Form>
)
QuickUserRegistrationForm = controlledForm(QuickUserRegistrationForm)
FullUserForm = controlledForm(FullUserForm)

Full documentation

FormProvider

The component that controls all form values and events. There may only be one instance of this component in the application.

Properties

NameTypeDefault valueDescription
validatorsValidator[][]An array of custom validators.

Form

The component responsible for form's registration and submit handlers.

Properties

NameTypeDefault valueDescription
namestringThe form id and name
classNamestringThe CSS classes for the native form component rendered by this component
onSubmitFunctionA submit handler function which receives the form values object by parameter: (formValues) => doSomething(formValues)
onResetFunctionA reset event handler function: () => doSomething()

FormControl

Component responsible for injecting the controller of a form into a child component.

Properties

NameTypeDefault valueDescription
formstringThe name of a registered form (or to be registered later by an Form component)
onChangeFunctionA change event handler function which receives the form controller by parameter: (formCtrl) => doSomething(formCtrl)
injectFunctionA function responsible for transforming the form controller into an object containing as key the name of the property to be injected and the value of the property: (formCtrl) => ({injectedFormNameProp: formCtrl.formName})

Injects

NameTypeDescription
formCtrlFormStateControllerThe form controller

FormStateController

NameTypeDescription
formNamestringThe name of the watched form.
validbooleantrue if the form is valid.
invalidbooleantrue if the form is invalid.
untouchedbooleantrue if all fields of the form are untouched (field blur).
touchedbooleantrue if any field of the form was touched (field blur).
pristinebooleantrue if all fields of the form never changed it's value since it's loaded or reseted.
dirtybooleantrue if any field of the form has changed it's value one or more times since it's loaded or reseted.
unchangedbooleantrue if all fields values of the form are exactly equals it's initial values.
changedbooleantrue if any field value of the form aren't exactly equals it's initial value.
valuesobject{string: string}The fields values of the form: {[fieldName]: [fieldValue]}.
filesobject{string: File[]}The selected files of each file field of the form.
fieldsobject{string: FieldStateController}The fields controllers of the form: {[fieldName]: [fieldCtrl]}
setFieldValueFunctionMethod to programmatically change a field value: props.formCtrl.setFieldValue('fieldName', 'newValue').
resetFunctionMethod to programmatically reset all form instances with this form name: props.formCtrl.reset().

Field

Component that injects an form's field control properties to it's child.

Properties

NameTypeDefault valueDescription
namestringThe name of the field.
formstringThe name of the field's form.
classNamestringThe CSS class to inject into it's component child.
requiredbooleanfalsetrue if the field is required.
patternstring|RegExpThe regex to validate the field value.
typestringtextThe input field type. Supports all types, but currently only the "email" and "number" types has out of the box validation.
integerbooleanfalsetrue if when the Field type property is "number" and should validate to integer value.
matchstringAnother field name that the value of this field should match.
minnumber|stringThe min number value of a field with type "number".
maxnumber|stringThe max number value of a field with type "number".
minLengthnumber|stringThe min string value length of a field.
maxLengthnumber|stringThe max string value length of a field.
initialValueDate|number|stringThe field's initial value.
injectFunctionA function responsible for transforming the Field component injection properties into an object containing as key the name of the property to be injected and the value of the property: (field) => ({injectedOnChange: field.onChange})
onChangeFunctionField change event handler called after "react-formctrl" state change cycle. (fieldCtrl) => foo(fieldCtrl)
onBlurFunctionField blur event handler called after "react-formctrl" state change cycle. (fieldCtrl) => foo(fieldCtrl)
onResetFunctionHandler called when the form that this field is attached is reseted. (fieldCtrl) => foo(fieldCtrl)

Injects

NameTypeDescription
namestringThe name of the field.
formstringThe name of the field's form.
classNamestringThe CSS class to inject into it's component child.
requiredbooleanfalsetrue if the field is required.
patternstring|RegExpThe regex to validate the field value.
typestringThe input field type.
onChangeHTMLEventHandlerThe field change event handler: (e) => handleChange(e.target.value).
onBlurHTMLEventHandlerThe field blur event handler: (e) => handleBlur(e.target.name).
valuestringThe current field value.
filesFile[]The selected files of the field.
ctrlFieldStateControllerThe field controller.

FieldStateController

NameTypeDescription
validbooleantrue if the field is valid.
invalidbooleantrue if the field is invalid.
untouchedbooleantrue if the field is untouched (field blur).
touchedbooleantrue if the field was touched (field blur).
pristinebooleantrue if the field never changed it's value since it's loaded or reseted.
dirtybooleantrue if the field has changed it's value one or more times since it's loaded or reseted.
unchangedbooleantrue if the field value is exactly equals it's initial value.
changedbooleantrue if the field value isn't exactly equals it's initial value.
valuestringThe value of the field.
filesFile[]The field selected files.
errorsValidationError[]An array of strings with all current validation errors of the field.
propsFieldStatePropertiesSome properties of the Field.

FieldStateProperties

NameTypeDescription
typestringThe input field type.
requiredbooleantrue if the input field is required.
patternstring|RegExpThe regex to validate the field pattern.
integerbooleantrue if when the Field type property is "number" and should validate to integer value.
matchstringAnother field name that the value of this field should match.
minnumber|stringThe min number value of a field with type "number".
maxnumber|stringThe max number value of a field with type "number".
minLengthnumber|stringThe min string value length of a field.
maxLengthnumber|stringThe max string value length of a field.
initialValueDate|number|stringThe field's initial value.

ValidationError

NameTypeDescription
keystringValidation error message key.
paramsobject{string: any}Validation error message parameters.
1.4.3

6 years ago

1.4.2

6 years ago

1.4.1

7 years ago

1.4.1-2

7 years ago

1.4.1-1

7 years ago

1.4.1-0

7 years ago

1.4.0

7 years ago

1.3.1

7 years ago

1.3.1-beta.1

7 years ago

1.3.0

7 years ago

1.3.0-beta.3

7 years ago

1.3.0-beta.2

7 years ago

1.3.0-beta.1

7 years ago

1.2.1

7 years ago

1.2.0

7 years ago

1.1.2

7 years ago

1.1.1

7 years ago

1.1.0

7 years ago

1.0.1

7 years ago

1.0.0

7 years ago

0.1.0-alpha08

7 years ago

0.1.0-alpha07

7 years ago

0.1.0-alpha06

7 years ago

0.1.0-alpha05

7 years ago

0.1.0-alpha04

7 years ago

0.1.0-alpha03

7 years ago

0.1.0-alpha02

7 years ago

0.1.0-alpha01

7 years ago