Form container is a lightweight React form container with validation (written in TypeScript). It allows you to use both HTML5 form validation and custom validation functions.
built with ❤️ at Appfocused
It provides your child form with 2 objects of props:
form
- all data on your form values, states, errors and warningsformMethods
- methods to bind your input controllers and manipulate the form model
- Material UI login form
form-container
withmaterial-ui
controls
Form Container is available as the form-container
package on npm.
Install it in your project with npm
or yarn
npm install form-container
or
yarn add form-container
form-container
exposes connectForm
function to connect an arbitrary form component (WrappedComponent
).
It does not modify the component class passed to it; instead, it returns a new, connected component class for you to use.
-
[
validators: ValidationRule[]
] (Array): An array of rules can be provided to validate the form model against them. Rules are executed in a sequence that is defined in the array. -
[
formConfig: IFormConfig
] (Object): An object contains initial configuration for the forminitialModel: Partial<T>
— object provides initial values to the form fieldsmiddleware: (props: T) => T & M
— function transforms props passed to the wrapped componentonInputBlur: (e: React.ForcusEvent<any>) => void
— function is called on every blur on an input field within the form. Adding a customonBlur
to the input field itself is not recommended, use this method instead
import * as React from 'react';
// bare minimum import
import { connectForm, IFormProps } from 'form-container';
// IFormProps interface contains the props that are passed down from form-container
interface IProps extends IFormProps {}
export class Form extends React.Component<IProps, {}> {
handleSubmit = (e: any) => {
e.preventDefault();
const { model } = this.props.form;
console.log(model);
}
render() {
const { validationErrors, touched } = this.props.form;
const { bindInput, bindNativeInput } = this.props.formMethods;
return (
<form onSubmit={this.handleSubmit}>
<div>
<label>
Required field
<input
{/* HTML attribute to validate required field */}
required={true}
{/* this is how you bind input to a form-container */}
{...bindNativeInput('test')}
/>
<small>{touched.test && validationErrors.test}</small>
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
);
}
}
// no custom validators
const validators: any[] = [];
const formConfig = {
initialModel: {
test: 'foo'
}
}
// attaching our Form to form-container with validation
export default connectForm(validators, formConfig)(Form);
// components/Form.tsx
import * as React from 'react';
import { connectForm, ValidationRuleFactory, Condition, IFormProps } from 'form-container';
interface IProps extends IFormProps {}
// arbitrary form component
export class Form extends React.PureComponent<IProps, {}> {
render() {
const { validationErrors, touched } = this.props.form;
const { bindInput } = this.props.formMethods;
return (
<form>
<div>
<label>
Test:
<input
{/* this is how you bind input to a form-container */}
{...bindInput('test')}
/>
<small>{touched.test && validationErrors.test}</small>
</label>
</div>
</form>
);
}
}
// custom validator
const hasMoreThan6Chars: Condition = value => (value ? value.length > 6 : true);
export const strongPassword = ValidationRuleFactory(
hasMoreThan6Chars,
'We recommend password to contain more than 6 characters for security purposes',
'warning'
);
// all validators for the form
const validators = [
isRequired('test'),
strongPassword('test')
];
// attaching our Form to form-container with validation
export default connectForm(validators)(Form);
Form Container exposes SubmissionError
, a throwable error which can be used to set submit validation for fields in response to promise rejection because of validation errors.
A wrapped form component has access to handleSubmit
via formMethods
:
- [
submit: (model) => Promise
]: A submit handler function which has access to the form model and returns a Promise.
Inside the provided submit
function you can throw a SubmissionError
which will be caught by handleSubmit
, and the submission errors will be set for each key in the form state under a key submitErrors
, e.g.:
throw new SubmissionError({
test: "Don't like this..."
});
// components/Form.tsx
import * as React from 'react';
import { connectForm, ValidationRuleFactory, Condition, IFormProps } from 'form-container';
interface IProps extends IFormProps {}
interface IFormModel {
test: string;
}
// arbitrary form component
export class Form extends React.PureComponent<IProps, {}> {
submit = (model: IFormModel) => {
return fetch('http://dummyurl.com', {
method: 'POST',
body: model
}) // your Promise based HTTP client of choice
.catch(error => {
const { data: { code }, status } = error.response;
if (status === 422 && code === 'xyz') {
throw new SubmissionError({
foo: "Back-end doesn't like this..."
});
} else {
/// handle other errors accordingly
}
});
};
render() {
const { validationErrors, submitErrors, touched } = this.props.form;
const { bindInput, handleSubmit } = this.props.formMethods;
return (
<form>
<div>
<label>
Test:
<input {...bindInput('test')} />
<small>
{(touched.test && validationErrors.test) || submitErrors.test}
</small>
</label>
</div>
<button onClick={handleSubmit(this.submit)} />
</form>
);
}
}
export default connectForm<IFormModel>()(Form);