react-fields
includes utilities for easily building forms from a data schema.
npm install --save react-fields
The following examples illustrate some of the different features and utilities of react-fields
.
react-fields
provides sensible defaults for quickly creating forms. To demonstrate, we'll create a simple login form using renderForm
:
import React from 'react';
import { render } from 'react-dom';
import { renderForm } from 'react-fields';
const schema = {
fullName: {
type: 'string',
rules: {
required: true
}
},
age: {
type: 'number',
rules: {
required: true
}
},
password: {
type: 'string',
rules: {
required: true,
min: 8
}
}
};
const SignupForm = () => {
const form = renderForm(schema, {
submit: values => console.log('Submitted:', values)
}, fields => (
<div>
<fieldset>
<h4>Personal Info</h4>
<label>Full Name</label>
{fields.render('fullName')}
<label>Age</label>
{fields.render('age')}
</fieldset>
<label>Password</label>
{fields.render('password')}
</div>
))
return (
<div>
<h2>Signup</h2>
{form}
</div>
)
};
render(<SignupForm/>, document.body);
renderForm
automatically infers what type of input component to render from the field type. Also, notice the stateless component SignupForm
. Since the form manages it's own state, we can easily render forms inside both stateful and stateless components. When the form is submitted, the submit
callback is called with the submitted field values if the validation succeeds.
The rules
defined in schema
are used for client-side validation. When invalid data is submitted, the fields will automatically show the errors and submit
will not be called.
react-fields
renders default components for different field types, but you may want to customize this. You can change the default field components to any controlled component with value
and onChange
props. We can do this in three levels:
- Override the field component by type using
fieldComponents
.
renderForm(schema, {
fieldComponents: {
string: SuperTextBox,
number: MyNumberBox
}
}, fields => (
// ...
));
- Override the field component by field using
fieldComponent
.
const schema = {
fullName: {
type: 'string',
fieldComponent: SuperTextBox
}
};
- Override the field component using
fields.render
.
renderFields(schema, fields => (
<div className="form-horizontal">
{fields.render('fullName', SuperTextBox)}
{fields.render('age', MyNumberBox)}
{fields.render('password')}
</div>
));
TODO: Document custom field component props
We don't want to specify the same options each time we render a form. react-fields
exports a createFormRenderer
helper that you can use. Here's an example of how this can be used within the organization of a larger app.
We define custom field components in their own directory.
// fieldComponents/SuperTextBox.jsx
export default const SuperTextBox = (props) => (
<input className="is-super" {...props}/>
);
Create our own custom renderer:
// utils.js
import { createFormRenderer } from 'react-fields';
import SuperTextBox from './fieldComponents/SuperTextBox';
export const renderForm = createFormRenderer({
fieldComponents: {
string: SuperTextBox
},
submitButton: onClick => (
<button onClick={onClick}>Custom Submit</button>
)
});
Now we can import the custom renderer instead of the vanilla one from react-fields
:
import { renderForm } from './utils'
const SignupForm = () => {
const form = renderForm(schema, {
// ...
}, fields => (
// ...
))
};
You can add custom validators to your fields with custom
. Asynchronous validation is supported as well.
import api from 'api'
async isRegisteredPhoneNumber(value, fieldName, attrs) {
const isValid = await api.isRegisteredPhoneNumber(value)
return isValid ? true : 'This phone number is invalid'
}
const schema = {
phone: {
type: 'string',
rules: {
match: /^(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}$/,
custom: [isRegisteredPhoneNumber]
}
}
};
TODO: document createValidator
Sometimes the server will validate the data and it might return validation errors. react-fields
expects there to be two kinds of server-side validation errors, form-level and field-level. Both of these cases can be handled with submit
. submit
can be an async function, resolving with an error payload if submitting the data was not successful.
const saveUser = async (data) => {
// For example, this action will always return an error.
return {
summaryError: 'Something went wrong in the API',
fieldErrors: {
fullName: 'Not a real name',
age: 'Invalid age'
}
}
}
renderForm(schema, {
submit: saveAccount,
afterSubmit: data => console.log(data, 'was valid and was submitted successfully')
}, fields => (
<div>
{fields.render('fullName')}
{fields.render('age')}
</div>
))
react-fields
provides an afterSubmit
callback that is called when submit
resolves without an error.