You shouldn't have to learn a library’s ridiculously complex API in order to do React forms.
react-uniformed allows you to easily create declarative React forms using only React Hooks. The simplicity of this library removes the pain of remembering another complex React library, allowing you to focus on your app's business logic. We built this library with exceptional performance so that you can maintain a great user experience regardless of your application's scale - try the performance story.
- ️💆🏾♂️ Simple API
- 🙅🏻♀️ Zero dependencies
- 📜 HTML standard validation
- 🚀 Controlled & Uncontrolled inputs support
NPM
npm install --save react-uniformed
Yarn
yarn add react-uniformed
import React from 'react';
import { useForm, useSettersAsEventHandler } from 'react-uniformed';
// useForm holds the state of the form (eg touches, values, errors)
const { setValue, values, submit } = useForm({
onSubmit: (data) => console.log(JSON.stringify(data)),
});
// compose your event handlers
const handleChange = useSettersAsEventHandler(setValue);
// jsx
<form onSubmit={submit}>
<label>Name</label>
<input name='name' value={values.name} onChange={handleChange} />
<button>Submit</button>
</form>;
Add validation to your form by setting the validators
property in useForm
and start validation by calling validateByName
or validate
. Then read the validation state from the errors
object.
import { useForm, useSettersAsEventHandler } from 'react-uniformed';
const { setValue, validateByName, errors } = useForm({
// Declarative HTML5 style form validation
constraints: {
name: { required: true, minLength: 1, maxLength: 55 },
// email & url types are validated using HTML standard regex
email: { type: 'email' },
date: {
// set the error message for required by using a non empty string
required: 'Date is required',
type: 'date',
// set the error message and constraint using an array
min: [Date.now(), 'Date must be today or later'],
},
},
// the onSubmit function is only called after the form passes validation.
onSubmit: (data) => console.log(JSON.stringify(data)),
});
// No configs for when to validate the form because useSettersAsEventHandler
// allows you to configure your event handlers how ever you want to.
// validate on change
// const handleChange = useSettersAsEventHandler(setValue, validateByName);
// validate on blur
const handleBlur = useSettersAsEventHandler(validateByName);
react-uniformed supports uncontrolled inputs that uses React refs to synchronize the state of the input in the DOM and the state of the form in the Virtual DOM. The uncontrolled input allows us to avoid expensive React renders on keyup or change.
import { useSettersAsRefEventHandler } from 'react-uniformed';
// useSettersAsRefEventHandler defaults to an on change event
const changeRef = useSettersAsRefEventHandler(setValue);
// name attribute is still required as the changeRef calls setValue(name, value) on change
<input name='name' ref={changeRef} />;
useSettersAsRefEventHandler
is generally only needed for larger forms or larger React VDOMs. In addition to the useSettersAsRefEventHandler
, react-uniformed also supports validation maps. Validation maps allows you to only validate the input that changed using validateByName
. There are several ways to accomplish this...
const { validateByName, errors } = useForm({
validators: {
// validators must return empty string for valid values
name: (value) => (value ? '' : 'email is required'),
},
});
// useConstraints supports mixing validators and constraints
const validators = useConstraints({
name: (value) => "name still won't be valid",
email: { required: true },
});
// when used with useSettersAsEventHandler the validator
// will call the validation that matches the current input element's name
const handleBlur = useSettersAsEventHandler(validateByName);
If you prefer to validate in one function, then you can do that as well
const {
// note: validateByName will call the validate function on each call
// but the error will be the one with the corresponding name.
validateByName,
validate, // validate all values
} = useForm({
validators(values) {
const errors = { name: 'name will never be valid', email: '' };
if (!values.email) {
errors.email = 'email is required';
}
return errors;
},
});
It should be noted that useForm
is just one layer of abstraction used to simplify the form building process. If you need more granular control and orchestration of your form, then you should avoid using useForm
in favor of other form hooks like useFields
, useTouch
, useValidation
, and useSubmission
. The following is a basic implementation of useForm
that you can use to compose your forms.
import { useCallback } from 'react';
import { useFields, useTouch, useValidation, useHandlers, useSubmission } from 'react-uniformed';
function useForm({ onSubmit, validators, constraints }) {
// tracks the input values
const { values, setValue, resetValues } = useFields();
// tracks the touch state of inputs
const { touches, touchField, resetTouches, isDirty } = useTouch();
// handles validation
const { validateByName, validate, errors, resetErrors, hasErrors } = useValidation(
validators || constraints,
); // this is not the real implementation
// composes a "form reset" function
const reset = useHandlers(resetValues, resetErrors, resetTouches);
// creates a validation handler that binds the values
const validator = useCallback(() => validate(values), [values, validate]);
// Guards against submissions until all values are valid
const { submit } = useSubmission({ onSubmit, validator, values, reset, disabled: hasErrors });
}