0.1.0 • Published 7 years ago

formtools v0.1.0

Weekly downloads
2
License
MIT
Repository
github
Last release
7 years ago

formtools

npm install formtools --save

Higher order components (HoC) for React to manage forms and fields.

Overview

These components are usefull for handling simple or medium-complex forms, and when you don't want to use tools like redux to manage the state of your fields.

The 'Field' HoC adds common behaviour to a form field, mainly normalization, validation, and keeping the field 'controlled'. The goal here is to keep your field completely stateless.

The 'Form' HoC acts as a state container with some extra functionalities. These functionalities are passed down as props.

These components are very unopinionated on how to inplement them in your app. They also don't depend on each other, which make them suitable for test.

Features

  • Keeps your fields presentational
  • Small footprint
  • Highly plexible and customizable
  • State is managed for you
  • Can be used with other tools easily

Basic usage

Field

// Field example A: just a controlled input

import React from 'react';
import { Field } from 'formtools';

function Input({ onChange, value }) {
    return (
        <input type="text" value={value} onChange={onChange} />
    );
}

export default Field({
    valueMap: event => event.target.value
})(Input);
// Field example B: more complex

import React from 'react';
import { 
    Field, 
    expect, 
    targetValue, 
    defaultFieldProps 
} from 'formtools';

function Input({ error, success, label, setRef, ...props }) {
    // determine the className for the input element
    let cx;
    if (error) cx = 'error';
    if (success) cx = 'success';

    return (
        <div className="field">
            <label htmlFor={props.name}>{label}</label>
            <input
                ref={setRef} // set ref, i.e. to call focus() in Form component
                className={cx}
                {...defaultFieldProps(props)} // helper which adds value, events, etc.
                type="text"
            />
            {error && <div className="errorMessage">{error}</div>}
        </div>
    );
}

export default Field({
    valueMap: targetValue, // helper: event => event.target.value
    normalize: val => val.toUpperCase(),
    validate: [
        // access child field properties within validation rules
        expect((val, _, field) => field('name').value.length > 0, 'Please fill in your name first'),
        expect(val => val.length >= 3, 'This value is to short'),
        expect(val => val.length < 9, 'This value is to long')
    ]
})(Input);
// Form example:

import React, { Component } from 'react';
import { Form } from 'formtools';

import Zipcode from './Zipcode';
import checkZipcode from './api';

@Form()
class RegistrationForm extends Component {

    handleSubmit = () => {
        // function from HoC
        const { validate, getValues, asyncError } = this.props;

        // validate every field and display error / successs if possible
        // return true of every field passes validation
        if (!validate()) return;

        const values = getValues();
        
        // example: check with api if zipcode is valid
        checkZipcode(values.zipcode)
            .then(data => {
                // on error manually overwrite error message of Zipcode field
                if (!data) asyncError('zipcode', 'Invalid zipcode!');
                // do something with all the field values :)
                else someAction(values)
            });
    }

    render() {
        return (
            <div>
                <Zipcode name="zipcode">
                ... other fields

                <button onClick={this.handleSubmit}>Submit</button>
            </div>
        )
    }
}

Docs

Field(config: Object): (Component): Component

Typical decorator pattern. Usage:

// es6
const Enhanced = Field({})(Component);

// es7
@Field({})

config

All of the config settings are totally optional.

  • valueMap

    A function which describes where to find the value in a onChange-handler. Typical usecase is 'event => event.target.value'.

  • normalize

    A function which takes a value and transforms it into another value. I.e.:

    val => val.toUpperCase()
  • validation

    Can be a function or an array of functions. Must be used together with to expect function. I.e:

    expect((value, ownProps, otherField) => value.length > 0, 'Error message on failure')

    expect calls your validator function with 3 arguments:

    • value: the value after normalization
    • ownProps: specific properties your form gave this field
    • field: a function which can be called with a field-name and returns an object containing value, error, success, dirty and touched

      In case of multiple validators, the validation will stop on the first error, so it is wise to sort your validators by priority.

Injected props

These props will be passed to the wrapped component:

  • Standard form events: onChange, onBlur, onFocus, onKeyDown, onKeyUp, onKeyPress,
  • value
  • name
  • error: string if there's an validation error, boolean (false) if not
  • success: boolean
  • setRef: allows HoC to access the inputs dom-node (optional)
  • Other props you've given..

Props

  • name required
  • Standard form events like onChange. All these event have a signature of (value, name)
  • Special event onValidChange, which will be called as soon as a value passes all validation
  • Own custom props, which will be passed down to the original wrapped component
  • initValue: value to start with, defaults to ''
  • aggressive: boolean which validates after each onChange instead of after the blur event

Validation behaviour

  • if no validators are set, success and error both will stay false

  • Validation will occur:

    • after the onBlur event
    • after the onChange event when the prop aggressive is set
    • after the validate method of the Form HoC is used

Form

Typical decorator pattern. Usage:

// es6
const Enhanced = Form()(Component);

// es7
@Form()

Exposed methods via props:

getValues()

Returns an object with fieldnames as key and their corresponding values.

validate(x?: string | string[], soft: boolean = false)

Validates all fields by default, unless a fieldname or an array of fieldnames is given, and returns true if all fields passed validation, false otherwise. Soft mode is disabled by default. When set to true, validation happens silently in the background without rendering error/success

clear(x?: string | string[])

Resets all fields by default, unless a fieldname or an array of fieldnames is given. All values of the field will be '' (empty), and the props success, error, touched and dirty will be set to false.

reset(x?: string | string[])

Same as clear(), except that reset will set the value back to initValue if possible

asyncError(fieldname: string, errorMessage: string)

Manually overwrite an error message of a field after a async call.

asyncErrorClear(fieldname: string)

Clears to above error and sets the validation before the async-error was set.

node(fieldname: string)

Returns a dom-node of a field. See setRef prop in Field.

event(type: string, handler: (value: any, name: string))

Allows you to subscribe to common events. Usage:

this.props.events(
    'onChange',
    (value, name) => console.log(`The value of ${name} is '${value}'`)
);

field(fieldname: string)

Return an object with data:

  • value
  • success
  • error
  • touched: field was at least focussed once
  • dirty: field was changed at least once
0.1.0

7 years ago