react-fields includes utilities for easily building forms from a data schema.
npm install --save react-fieldsThe 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.