Usetheform: React Library for Composing Declarative Forms
Zero dependencies, it only uses the Context API and React Hooks.
Introduction
A web form consists of a collection of HTML field elements grouped within a <form>
tag. HTML supports different field elements, like for instance:
<input />
which defines an HTML form for user input<textarea />
which defines a multi-line input control (text area)<select />
which defines a drop-down list
For a complete list of all the HTML form elements supported, please refer to w3schools form elements.
Semantically, creating forms is easy. As long as the form’s complexity is low, you may not need a library to handle form submissions. However, the form’s complexity — in my experience — can increase quite easily. In most instances, we will at the minimum have to deal with use cases like:
- Inputs validation, sync and async
- Dynamically adding and removing fields
- Form state management
React does not come with a prebuilt solution for overcoming those cases, but it provides a very useful technique called “controlled components” which may facilitate developers in addressing them.
If you wanted to know more about it, please refer to react controlled components.
Although this technique gives control over form’s input values, it does not accomplish aspects such as field’s validation (sync and async). Thinking on developing custom strategies within your projects to address them it might easily turn into a nightmare.
Luckily, out there, there are many React libraries which are valid solutions. For instance: Formik, ReduxForm, Final Form, React-Hook-Form and many others.
In today article we are going to talk about usetheform a react library for composing declarative forms and managing their state. Let’s start it.
Installation
To install the package run the following npm command:
npm i usetheform --save
NPM package: https://www.npmjs.com/package/usetheform
GitHub project: https://github.com/iusehooks/usetheform
Documentation: https://iusehooks.github.io/usetheform/
Basic concepts
Usetheform, as any other react library, provides basic components and exposes a declarative API for composing HTML forms. Let’s analyze some of its core components and then create our first basic form.
Core Components:
<Form />
renders all the fields and keeps the entire form state synchronized.<Collection />
creates a nested piece of state within a Form and it can can be of type: object or array.<Input />
renders all the inputs of type listed at: W3schools Input Types and accepts as props any HTML attribute listed at: HTML Input Attributes.<Select />
creates a drop-down list and accepts as props any HTML attribute listed at: HTML Select Attributes.<TextArea />
renders a textarea element W3schools Textarea and accepts as props any html attribute listed at: Html Textarea Attributes.
Our First Basic Form:
Well, we just created a very simple <Form />
with an initial state and the following props:
- onSubmit : function invoked on Form submission.
- onChange: function invoked when any of the Form’s fields change their value.
- onInit: function invoked when the Form is initialized.
As result of that, once users press the submit button without changing any inputs value, we should expect to log into browser’s console the form state as following:
Output:
{ name: 'foo', color: 'BLUE', option1: 'option1' }
Now, let’s make the form more interesting by adding a bit of complexity. We want to have the following state logged into browser’s console after form submission:
Expected Output:
{ user: { name: 'foo', lastname: 'pluto' },
colors: ['BLUE', 'GREEN', 'RED'] }
Analyzing the expected output we can see an object named user within the form state which holds two props name, lastname and an array named colors with three items.
The <Collection />
component can easily get that job done. Let’s see how.
Simple? isn’t it? <Collection />
components in usetheform create a nested piece of state within a <Form />
, they can be easily nested to compose more complex form states. For instance:
In the above example the initial state was not passed as prop because usetheform provides two ways for initializing its fields (Collections, Inputs, Selects, TextAreas):
- By passing the initial value down using the initialState prop.
- By passing the initial value down using the value prop for any
<Input />
,<Collection />
,<Select />
,<TextArea />
except for the<Input />
of type checkbox and radio where we have to pass a value prop but also indicate if we want that value to be set as default using the checked prop.
So at form submission the output will be:
Reducers
Reducers functions specify how the form’s state change. They can be applied to any component: <Form />
, <Input />
, <Collection />
, <Select />
and<TextArea />
.
Basic usage
The reducers prop passed to <Collection reducers={fullNameFN} />
can be either of type array or function.
(nextValue, prevValue, formState) => nextValue
When either the input field name or lastname change, the value of fullName is going to be the result of the concatenation: name + lastname.
Use cases and implementations might be numerous and I leave them to your imagination and your practical needs. The next topic I would like to analyze is one of the most important aspects of writing HTML forms: the form validation.
Form Validation
Forms validation is one of the cardinal point when you write HTML forms. Usetheform provides APIs for dealing with sync and async validations.
Basic usage: Sync Validation
useValidation hook provides the sync validation logic for the fields <Input />
, <Collection />
, <Select />
and<TextArea />
.
const [status, validationAttr] = useValidation([...functions])
It takes as argument an array of functions which will be invoked following the array order.
In the above example another hook named useForm has been used to create a component named Sumbit which is a button enabled only if form passes all the validations functions added.
Basic usage: Async Validation
useAsyncValidation hook provides the async validation logic for the fields <Input />
, <Collection />
, <Select />
and<TextArea />
.
const [asyncStatus, asyncValidationAttr] = useAsyncValidation(fn);
It takes as argument a function which receives the value of the field and returns a promise. If the form contains at least one async validator function applied to any of its field, the value isValid of the useForm hook, which represent whether or not the form is valid, will be false until all the async validators are not resolved with success.
The asyncStatus is an object which holds two props: status
and value
.
The prop value
is whatever value the promise returns.
The prop status
can be one of the following:
undefined
: async validation did not start yetasyncStart
: async validation has startedasyncSuccess
: the promise has been resolved with successasyncError
: the promise has been rejected
Now that we saw how sync and async form validation can be achieved, before getting to the conclusions, I would like to show how fields can be either added or removed dynamically.
Addition/Removal: Fields or Collections
Dynamic addition or removal of form fields are often recurrent requirements that as developers we have to deal with. The following codesandbox implementation will show how it can be easily achieved using usetheform.
For further details about the API reference, please refer to the doc: https://iusehooks.github.io/usetheform/ where you can also learn how to use the following hooks:
- useField: a hook which allows to build a custom input primitives.
- useForm: a hook that returns helpers and the current state of the form.
- useCollection: a hook which allows to build a custom Collection of type object or array.
Another live example you can play with:
Others CodeSandbox Examples
- Examples: Slider, Select, Collections etc..: Sandbox
- Various Implementation: Sandbox
- Wizard: Sandbox
- FormContext: Sandbox
- Material UI — React Select: Sandbox
Conclusion
Usetheform could be another valid choice among many others react libraries out there. At the moment of this writing the version of the library is the 3.0.0 and the gzipped bundle size is 7.4kB.
A big thanks to marco sparagna for being the reviewer of this article
I hope you enjoyed reading the article. 👏 Claps are welcome!