Writing Input Components

Flum does not provide any actual input components - that's entirely up to the developer to build. There are two approaches you can take to creating a component that can integrate with Flum - using the FormComponent wrapper component provided or directly hooking into Flum's context API. The first approach is the recommended way to do things, while the second allows for slightly more control.

Using the wrapper

The provided wrapper component does all the work of integration and translation between Flum and your component. It gives you data and an onChange hook which you can pass to the form component you are wrapping, the same controlled API you are used to using with react.

You will need to provide the wrapper an id via props. This is a string that will be used as that components key in state. For example, if you gave a component the key firstName then the resulting state will look like so:

{
    firstName: {
        value: null,
        error: null,
        ...
    }
}

Additionally you can provide the component a validation string and additional validation functions. The validation string is a pipe separated collection of validators that need to be run on the components data. For example, the validation string letters|min:10 will result in a validation function letters and then a validation function called min being run against the component's data. Notice how the min:10 validator has the trailing :10 - that just means that the value 10 will be given as arguments to the validation function.

Validation strings have been split into two separate props - localValidation and globalValidation - and are just a way to separate when validation rules are applied. localValidation is applied on every state change, while globalValidation is applied on submit (kinda).

import { Form, FormComponent } from 'flum'


const App = () =>
    <Form>
        <FormComponent id="name">
            {({value, onChange}) => 
                <input value={value} onChange={e => onChange(e.target.value)} />
            }
        </FormComponent>
    </Form>

As an alternate way of using this wrapper, you can give it a component via props instead of children. For this to work your component needs to be a react class. Using this method means that the wrapper can grab custom validators directly from the input component.

import { Form, FormComponent } from 'flum'

class Input extends React.Component {

    static validators = {
        customValidator({value}) {
            if (value) return {valid: true}
            return {valid: false, error: "Invalid"}
        }
    }

    render() {
        const {value, onChange} = this.props
        return <input value={value} onChange={e => onChange(e.target.value)} />
    }
}

const App = () =>
    <Form>
        <FormComponent id="name" component={Input} localValidation="customValidator" />
    </Form>

The wrapper does of course do more than this, check out the API docs for the fleshy details.

Using Context

The context API consists of two methods - getField(id: string) and onChange(field: Object, id: string) - that are essentially getters and setters for state. A good practice would be to use onChange in componentDidMount to set the initial state and field properties.

A field follows the following structure. This data must be given along with every onChange call.

type Field = {
    value: ?any,
    error: ?string,
    valid: boolean,
    localValidation: ?string,
    globalValidation: ?string,
    validators: ?{[key: string]: Function}
}

Here is an example component built using context.

import React, { Component } from 'react'

export default
class FormComponent extends Component {

    static contextTypes = {
        form: PropTypes.object
    }

    /* Our field state is initialized with validators, validation
     * strings and null data. 
     * */
    componentWillMount() {
        const {form} = this.context
        const {id, localValidation, globalValidation, validators} = this.props

        form.onChange({
            localValidation,
            globalValidation,
            validators,
            value: null,
            error: null,
            valid: true
        }, id)
    }

    /* Whenever the data changes, pass the new data
     * along with the current state of the form.
     * */
    onChange = (value: any) => {
        const {form} = this.context
        const {id} = this.props
        const data = form.getField(id)

        form.onChange({
            ...data,
            value
        }, id)
    }

    render() {
        const {form} = this.context
        const {id} = this.props

        const {value} = form.getField(id)

        return <input value={value} onChange={e => this.onChange(e.target.value)}/>
    }
}