Base Form component for building convenient forms for React.
- Controlled Form, i.e. it accepts input values as a JSON object.
- Simple API that handles deeply nested values and collections.
- Flexible and convenient validation that allows to validate inputs as user types.
- Allows to easily turn any existing component into a Form Input component.
npm install --save react-form-base react-form-base provides a Form base class which expects to work together with Input components. An Input is any component that consumes three properties: value, error and onChange. It also has to provide it's value as first argument to onChange function supplied in props. For existing ready-for-use input bindings take a look on:
Most of form use-cases with examples are revealed in Demo Application. Details on how to run it locally are at the end of README.
Most of forms developers deal with are quite complicated and encapsulate vast amount of validation and rendering logic. After some basic setup described in the Wiki your form may look like following:
class UserForm extends Form { validations = { 'email': ['presence', 'email'], 'fullName': 'presence', 'address.city': 'presence', 'address.line': { presence: true, format: /^[\w\s\d\.,]+$/ } }; $render($) { return ( <div> <TextField {...$('email')} label="Email" /> <TextField {...$('fullName')} label="Full Name" /> <Select {...$('address.countryId') options={countryOptions} label="Country" /> <TextField {...$('address.city')} label="City" /> <TextField {...$('address.line')} label="Address" /> <button onClick={this.save.bind(this)}>Submit</button> </div> ); } }If your form is small enough, you might want to render it inline instead of defining separate form component. In this case you may pass renderer function as only form's child. This function takes form's $ function as argument for convenience. Note that you still need to define static validation rules for the Form to be able to use validations.
<Form {...bindState(this)} validations={{ email: ['presence', 'email'], fullName: 'presence' }}> {$ => ( <div> <TextField {...$('email')} label="Email" /> <TextField {...$('fullName')} label="FullName" /> <button onClick={this.registerUser}>Register</button> </div> )} </Form>$(name),input(name)- returns a set of properties for input with a given name.nameis a dot-separated string, i.e.'foo.bar'(forbarproperty nested in object underfoo), or'foos.1'(value at index 1 offoosarray), or'foos.2.bar'(barproperty of object at index 2 offoosarray)get(name)- returns a value for a given name. For example, if you have an attributes like{foos: [{bar: 'baz'}, {bar: 'bak'}]}, you might have:this.get('foos') // => [{bar: 'baz'}, {bar: 'bak'}]this.get('foos.1') // => {bar: 'bak'}this.get('foos.1.bar') // => 'bak'this.get() // returns whole form's attributes object
set(name, value)- sets avaluefor an input with a specifiedname.set(object)- sets multiple values at once. Each key in the object corresponds to input name, and values are input values.merge(name, value)- merges givenvalueobject with value of input with a givenname. Should be used when working with nested forms.push(name, value)- pushes avalueto an input (which is treated as array) with a givenname.remove(name, index)- removes an item of an input (which is treated as array) with a givennameat indexindex.each(name, iteratee)- iterates withiterateeover items in an input with a givenname, passing item and index toiteratee.map(name, iteratee)- maps withiterateeover items in an input with a givenname, passing item and index toiteratee.mapExtra(path, iteratee)- maps withiterateeover items in an input with a givenname, passing item and index toiteratee. Makes additional iteration that passesnulland length of items toiterateegetValidationErrors()- returns validation errors.performValidation()- runs validation routines and sets errors.ifValid(callback)- runs validation routines, sets errors, and executescallbackif there were no errors.getErrors()- returns an errors object.getError(name)- returns an error for an input with a givenname.setErrors(errors)- setserrors(object) as form's errors.save()- ifthis.props.validateOnSaveistrue(which is default value), performs validation and callsthis.props.onRequestSave(this.get(), this);if there were no errors. ifvalidateOnSaveproperty isfalse, callsthis.props.onRequestSave(this.get(), this);immediately.reset(attrs = {})- callsthis.props.onChange(attrs)and clears errors.
| Prop Name | Spec | Description |
|---|---|---|
attrs | PropTypes.object.isRequired | Form's attributes - the values of form's inputs |
onChange | PropTypes.func | A callback that is called whenever form's input changes it's value. Form's attrs are passed to it. Typically has a form of (formAttrs) => this.setState({ formAttrs }) |
clearErrorsOnChange | PropTypes.bool, defaults to true | If input has an error on it and this property is enabled, error will be cleared when input changes its value |
validateOnChange | PropTypes.bool, defaults to true | If form has input validations defined, and validation routines were called with unsuccessful result, enabling this property will re-validate input when its value changes |
validateOnSave | PropTypes.bool, defaults to true | If true, on save method call form will run validations first and execute onRequestSave callback only if there were no errors |
onRequestSave | PropTypes.func | This callback is called in Form#save method, passing form's attrs and form object itself to it |
react-form-base's Form is a controlled form component, which means that attributes it works with are supplied from outer component in props. This also means that proper (yet trivial) onChange function should be also supplied.
import React, { Component } from 'react'; import MyForm from 'my-form'; class Page extends Component { state = { item: {} }; saveItem = (item, form) => { // stubbed AJAX request code (with axios-like error) post('/items', { item }) .then(() => this.setState({ item: {} })) .catch((err) => form.setErrors(err.response.data)); }; render() { return ( <MyForm attrs={this.state.item} onChange={(item) => this.setState({ item })} onRequestSave={this.saveItem} /> ); } }react-form-base also ships bindState(component, key = 'form') helper function that will generate { attrs, onChange } props object for a given component. For instance, form from example above using this helper will look like so:
import React, { Component } from 'react'; import { bindState } from 'react-form-base'; import MyForm from 'my-form'; class Page extends Component { saveItem = (item, form) => { // ... }; render() { return <MyForm {...bindState(this, 'item')} onRequestSave={this.saveItem} />; } }Hugs and thanks to ogrechishkina for her support and building all of the CSS for demo application.
$ git clone git@github.com:akuzko/react-form-base.git $ cd react-form-base $ npm i $ cd demo $ npm i $ gulp MIT