Declarative React hook for gathering and validating form data without unnecessary re-renders.
Links:
1. Demo
2. NPM
3. GitHub
4. Medium
npm install use-reactive-form
yarn add use-reactive-form
interface IFormData = {
user: string;
books: {
title: string;
author: string;
}[]
}
const fields: IFormData = {
user: '',
books: [
{
title: '',
author: '',
}
],
}
import { array, object, string } from 'yup';
// ...
const schema = object().shape({
user: string().required('This field is required')
.max(20, 'Character limit exceeded'),
books: array().of(object().shape({
title: string().required('This field is required'),
author: string().required('This field is required'),
})),
});
import { IUseReactiveForm } from 'use-reactive-form';
// ...
const config: IUseReactiveForm<IFormData> = {
fields,
schema,
validateOnChange: true
};
{
fields: T; // Form fields / structure
deps?: any[]; // Array of dependencies that trigger re-render
schema?: any; // Validation schema
separator?: string; // Separator for name property of inputs. _ is set by default
validateOnChange?: boolean; // Validate on input change
actionOnChange?: (values: T) => void; // Fire function on input change
updateTriggers? string[]; // Array of name attributes whose change triggers re-render
}
const { values, ref, update, validate, clear } = useReactiveForm<IFormData>(config);
/**
values - object with current form state
ref - reference to <form> tag
errors - object of errors after validation
validate() - function which validates the form
clear() - function which clears form values form and errors
update() - function which re-renders form. It is needed when you dynamically add/remove fields.
**/
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (validate()) {
console.log(values);
} else {
console.log(errors);
}
};
return (
<form ref={ref} onSubmit={onSubmit}>
<div>
<input type='text' name={'user'} defaultValue={values.name}/>
{ errors.user.error && <p> {errors.user.error} </p> }
</div>
{
values.books.map((b, i: number) => (
<div key={`book${i}`}>
<input type='text' name={`books_${i}_title`}/>
<input type='text' name={`books_${i}_author`}/>
</div>
))
}
<button type='submit' onClick={onSubmit}> Submit </button>
</form>
)
Notice, that you have to describe name
attribute as a path to the key in your form object.
Instead of common separators (.
, []
) use _
or your separator described in config
.
To get error message use errors
. It is an object with the same structure as your
form object, but instead of just values, it contains object { value: string, error: string }
.
Therefore, error message for user
field located in errors.user.error
.
Any action triggered on the <input/>
will provide it with one of the following classes: touched
, dirty
or invalid
.
If you want to add some fields dynamically, you need to use update()
function.
Let's say you want to add a new book. You will need to copy values
and push a new book object to the values.books
array.
const addBook = () => {
update({
...values,
books: [...values.books, {
title: '',
author: ''
}]
});
};
<button type='button' onClick={addBook}> Add book </button>
actionOnChange
is a parameter, which you may want to set to true
when you have to fire
a function when any of the inputs value changes. It may be desirable when you submit form dynamically.