A minimal React useForm hook
A deep dive into building a minimal form handling solution in React that tackles async validation, race conditions, and type safety.
Lately I’ve been coding a lot of forms. Form handling in React can be complex, especially when dealing with asynchronous validation, asynchronous submission, and type safety, precisely the features the in-house solution my team is using lacks of. So, I decided to create a minimal solution that would address these pain points.
Real world usage
We are going to use a form like this as the driving example:
Our useForm
hook must:
- Be type safe: We want full type-safety and autocompletion.
- Support asynchronous validation: We want to allow validation that might perform API calls, so it must support asynchronous validations with proper cancellation of outdated requests
- Communicate its state:
- submitting/valid states: Used to enable/disable form controls as corresponding
- focused/valid inputs state: Used to control which message (error or warning) to show
- Async submission: Usually submission involves API calls, so this must be supported
- Developer Experience: Simple API that doesn’t compromise on functionality nor creates a bloated chunk of code.
The console output shows the sequence of events and how our form handler manages them.
The interface
Our useForm
will have the following signature:
The FormOptions<T>
interface shows our focus on async operations. Notice the validate
function receives an AbortSignal
- this is crucial for handling race conditions in async validation.
And our Form<T>
model is defined as:
Implenting our example
Using the above interface we can easily implement our example roughly as this:
The implementation
Validation
The core part is handling async validation correctly. Here’s how we manage it:
To prevent unnecessary API calls, we debounce the validation:
This setup ensures that:
- Only the most recent validation result is applied
- Unnecessary API calls are cancelled
- We don’t waste resources validating intermediate states
Async submission
Form submission often involves API calls, and handling the submission state correctly is crucial for UX. Our handler takes care of this automatically:
Entire implementation
Here’s how the entire implementation looks like:
Conclusion
Building this form handler have solved my specific needs around async operations and type safety. While there are many form libraries available, sometimes building a custom solution that perfectly fits your needs is the right choice.