-
Notifications
You must be signed in to change notification settings - Fork 2
Home
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. The most strait-forward Input component may look like so:
function TextField(props) {
const { value, error, onChange, ...rest } = props;
return (
<div>
<input value={value} onChange={(e) => onChange(e.target.value)} {...rest} />
{error &&
<div className="error">{error}</div>
}
</div>
);
}
TextField.propTypes = {
value: PropTypes.string,
error: PropTypes.string,
onChange: PropTypes.func
};
Generally, you can use form's $
method when rendering your inputs. This method generates a set of properties that are consumed by input components.
class UserForm extends Form {
render() {
return (
<div>
<TextField {...this.$('firstName')} />
<TextField {...this.$('lastName')} />
<button onClick={this.save.bind(this)}>Save</button>
</div>
);
}
}
Form's $
method accepts a string as an argument. This string represents input name - a dot-separated value that specifies input's nesting and/or position in collection. For example, 'address.city'
, 'favoriteLanguages.1'
, 'items.0.amount'
are all valid input names. Form example bellow deals with address object nested under address
property of it's attrs
.
class RegistrationForm extends Form {
render() {
return (
<div>
<TextField {...this.$('email')} />
<Select {...this.$('address.country')} options={countries} />
<TextField {...this.$('address.city')} />
<TextField {...this.$('address.streetLine')} />
<button onClick={this.save.bind(this)}>Save</button>
</div>
);
}
}
Custom onChange
handler is passed in additional parenthesis after $
function call. It's first argument is handler itself, and subsequent values are bound to handler function call.
class OrderForm extends Form {
changeItem(value) {
this.set({
item: value,
amount: null
});
}
render() {
return (
<div>
<Select {...this.$('item')(this.changeItem)} options={['Item 1', 'Item 2']} />
<Select {...this.$('amount')} options={['10', '50', '100']} />
<button onClick={this.save.bind(this)}>Save</button>
</div>
);
}
}
react-form-base
's Form
gives you a mechanism to define any validation of your choice that can be applied to input values as user changes them. First step is to define Validation Rules. If you have a base form in your application with a shared functionality, you should define static validations
property there:
import Form from 'react-form-base';
// you can use third-party validation in rules:
import isEmail from 'validator/lib/isEmail';
class BaseForm extends Form {
static validations = {
presence: function(value) {
if (!value) return 'cannot be blank';
},
email: function(value) {
if (value && !isEmail(value)) return 'should be an email';
},
numericality: function(value, options) {
const { greaterThan } = options;
const fValue = parseFloat(value);
if (isNaN(fValue)) return 'should be a number';
if (greaterThan != undefined && fValue <= greaterThan) {
return `should be greater than ${greaterThan}`;
}
}
};
}
And now you can use those rules in your application forms:
import BaseForm from 'base-form';
class UserForm extends BaseForm {
validations = {
email: ['presence', 'email'],
fullName: 'presence',
age: { presence: true, numericality: { greaterThan: 18 } }
};
render() {
return (
<div>
<TextField {...this.$('firstName')} />
<TextField {...this.$('email')} />
<TextField {...this.$('age')} />
<button onClick={this.performValidation.bind(this)}>Validate</button>
</div>
);
}
}
If you don't have extra logic based on render method (such as implementing
rendering in base form and calling super.render(someContent)
from child
forms), and you want to make things a little bit more DRY, you may declare
your form's rendering using $render
method that accepts input-generation
function as argument. Thus, removing the this.
prefix in inputs:
class UserForm extends Form {
$render($) {
return (
<div>
<TextField {...$('firstName')} />
<TextField {...$('lastName')} />
<TextField {...$('email')} />
</div>
);
}
}
This form of rendering declaration is also very useful when working with
nested forms, since it has a special nested
method that will generate
onChange handler for nested form for you:
{this.map('items', (_item, i) =>
<ItemForm key={i} {...$.nested(`items.${i}`)} />
)}
Of course, since $
is argument in this method, you may use any name for
this variable that you find suitable.
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>