A 2kb library for building forms and validation with uncontrolled fields in React.
Install the package with npm.
npm install --save react-uncontrolled-form
Import the Field
and Form
components.
import { Field, Form } from 'react-uncontrolled-form';
class MyForm extends React.Component {
handleSubmit(form) {
}
render() {
return (
<Form onSubmit={form => this.handleSubmit(form)}>
<Field>
{() =>
<input name="email" type="email"/>
}
</Field>
<Field validators={[minLength(6)]}>
{state =>
<div>
<input name="password" type="password"/>
<div>{state.message}</div>
</div>
}
</Field>
<button>Submit</button>
</Form>
);
}
}
The Field
component requires its children to be a function that returns JSX. When a Field
renders it will call the children function, passing state
and validate
as arguments, and render the output. During the render, if an element with a name
attribute is found, it will be registered with the Form
and have the necessary props passed to it.
The Form
component will render a <form>
element and validate its Field
components on submission. A form
prop, containing essential field registration functions, is automatically passed to all child components to be used by the Field
component. If you nest a Field
component inside another component, you must pass along the form
prop to it.
When a form is submitted, all fields will have their validators run. The onSubmit
event handler is passed an object containing the form's validity, values, and invalid fields.
class MyForm extends React.Component {
handleSubmit(form) {
if (form.valid) {
console.log('Form values:', form.values);
}
},
render() {
return (
<Form onSubmit={form => this.handleSubmit(form)}>
<Field>
{() =>
<input name="username"/>
}
</Field>
<button>Submit</button>
</Form>
);
}
}
You can pass initial values to your fields by adding the values
prop to the Form
.
class MyForm extends React.Component {
render() {
const values = {
firstName: 'Eric',
lastName: 'Valadas'
};
return (
<Form values={values}>
<Field>
{() =>
<input name="firstName"/>
}
</Field>
<Field>
{() =>
<input name="lastName"/>
}
</Field>
</Form>
);
}
}
Validators are simply functions that return an error message.
function minLength(length) {
return value => {
if (!value || value.length < length) {
return `Must be at least ${length} characters`;
}
};
}
class MyForm extends React.Component {
render() {
return (
<Form>
<Field validators={[minLength(3)]}>
{state =>
<div>
<input name="username"/>
<div>{state.message}</div>
</div>
}
</Field>
</Form>
);
}
}
The validation message will be passed to your Field component via state.message
. If a validator does not return a message, the field is considered valid.
class MyForm extends React.Component {
render() {
return (
<Form>
<Field>
{state =>
<div className={'form-group' + (state.valid ? '' : ' has-error')}>
<label className="control-label" htmlFor="username">Username</label>
<input name="username" id="username" className="form-control"/>
<span className="help-block">{state.message}</span>
</div>
}
</Field>
</Form>
);
}
};
All fields have a validate
function that will call each validator in the validators
prop. By default, validate
is only called when the form is submitted. However, this function is passed to the render prop and can be called whenever you like. Here's an example of validating a field as you type.
class MyForm extends React.Component {
render() {
return (
<Form>
<Field validators={[minLength(3)]}>
{(state, validate) =>
<div>
<input name="username" onChange={validate}/>
<div>{state.message}</div>
</div>
}
</Field>
</Form>
);
}
}
You can also add field messages to your form which can be useful for things like server-side validation errors after submitting the form.
class MyForm extends React.Component {
constructor(props) {
super(props);
this.state = {messages: {}};
}
handleSubmit() {
this.setState({
messages: {username: 'Username already exists'}
});
},
render() {
return (
<Form messages={this.state.messages} onSubmit={form => this.handleSubmit(form)}>
<Field>
{state =>
<div>
<input name="username"/>
<div>{state.message}</div>
</div>
}
</Field>
</Form>
);
}
}
If you need access to another field's value for validation, you can do so by adding a ref
to your form. To retrieve the value, call the form's values
function.
function match(compareValue, fieldName) {
return value => {
if (compareValue() !== value) {
return fieldName + ' does not match';
}
};
}
class MyForm extends React.Component {
render() {
return (
<Form ref={form => this.form = form}>
<Field>
{() =>
<input name="email" type="email"/>
}
</Field>
<Field
validators={[match(() => this.form.values().email, 'Email')]}
exclude={true}
>
{state =>
<div>
<input name="confirm-email" type="email"/>
<div>{state.message}</div>
</div>
}
</Field>
</Form>
);
}
}
The Form
component uses a form
prop to provide field registration functions, initial values, and messages to its child components. If you want to nest a Field
component inside another component, you will need to pass the form
prop manually.
class MyComponent extends React.Component {
render() {
return (
<Field form={this.props.form}>
{() =>
<input name="username"/>
}
</Field>
);
}
}
class MyForm extends React.Component {
render() {
<Form>
<MyComponent/>
</Form>
}
}
The value of any field can be transformed by passing a transform
prop to the Field
component. For example, a checkbox input will have the value "on"
if no value is provided, but it can be changed to a boolean value if that is preferred.
class MyForm extends React.Component {
render() {
return (
<Form>
<Field transform={value => Boolean(value)}>
{() =>
<label>
<input name="agree-to-terms" type="checkbox"/>
I agree to the terms
</label>
}
</Field>
</Form>
);
}
}
If you create a custom input, or use a component like react-select, which passes a custom value to the onChange
handler, the Field
component will still register the value. The field's value will be set to the argument passed to onChange
. The Field
component passes an onChange
prop to the child with a name
attribute, so you do not need to set one yourself.
With the use of a couple optional props, react-select works just as you'd expect. In this example, simpleValue
and multi
set to true
will produce a value of "one,two"
. Using this in combination with the transform
prop on the Field
, a value of ["one", "two"]
can be achieved.
class MyForm extends React.Component {
render() {
<Form>
<Field transform={value => value.split(',')}>
{state =>
<Select
name="numbers"
value={state.value}
simpleValue={true}
multi={true}
options={[
{value: 'one', label: 'One'},
{value: 'two', label: 'Two'},
]}
/>
}
</Field>
</Form>
}
}
Prop | Description |
---|---|
onSubmit |
A callback functiononSubmit({valid: [bool], values: {...}, invalidFields: {...}} |
values |
An object that will provide values to the form's fields |
messages |
An object that will provide messages to the form's fields |
Prop | Description |
---|---|
form |
An object containing the form's registration functions, initial values, and messages |
validators |
An array containing the field's validators |
exclude |
A boolean that, when set to true , will exclude this field from the form's values |
transform |
A function to transform the field's value |