A fast, completely type-checked React form state manager. Supports nested objects/arrays.
React using javascript is great, but for larger projects, it can get quite tedious when refactoring code. For example, when renaming a property, vscode (for example) does not always replace every occurrence, which can lead to frustrating search-and-destroy bug scenarios.
Fortunately, there is typescript. When set up correctly, typescript will whine (typescript: 😠) about variables and functions that you try to access but don't exist. This makes maintaining a big project so much easier because it lets you know where things do not seem right.
✔️ Basic example
Recently, I had to build an application for a client, which contained a lot of large forms. A few examples of forms I had to create:
Great! Let's use a form management library. I had used Formik and react-hook-form before, they were fine and I used Formik for a while.
Because there were a lot of big forms, I had to refactor these a lot, when renaming a field for example. Unfortunately, Formik does not warn you if forget to rename an input name. This required me to manually double-check inputs when renaming fields to see if its field name was set up correctly.
This was the main problem I had with these form management libraries. They are great for React with javascript, but with typescript, it would be nice to have a type-checked solution.
Secondly, Formik was too slow for the questionnaire form (which contains a list of 50+ inputs), it caused everything to rerender each time. (Not the case with react-hook-form) 😕
I looked for a decent type-checked React form manager, but couldn't find any. After all of this, I decided to write my own!
When starting this project, there were a few things I wanted to make sure to include:
A nested field is a field inside an object. When using Formik, you use a dot to specify nested fields:
✔️ Using Formik with nested fields
// `user` is an object
<Formik initialValues={{ email: "john@example.com", user: { name: "John" } }}>
{() => (
<Form>
<Field name="email" />
{/* Using a dot */}
<Field name="user.name" />
</Form>
)}
</Formik>
Unfortunately, there is no way to type-check this, typescript cannot know that "user.name"
(as a string) means the "name"
field in the "user"
field. Typescript can only type-check field names in the upper-most object.
The solution is child forms! Like a normal form is based on an object containing initial values, a child form is a form based on an object field in another form.
✔️ Using typed-react-form with nested fields
// `user` is an object
const form = useForm({ email: "john@example.com", user: { name: "John" } });
// The "user" string is type checked and is a key of form.values
const userForm = useChildForm(form, "user");
return (
<form>
<FormInput form={form} name="email" />
{/* The "name" string is type checked and is a key of userForm.values */}
<FormInput form={userForm} name="name" />
</form>
);
Typescript knows that values in userForm
come from the value of the "user"
field in form
, its parent.
This way, type-checking is possible:
Great! Problem 1 is solved! 😀
Most of the form libraries rerender the whole form when something changes, in my case, this is a problem, because I have array fields containing 50+ items, which would all be rerendered and cause lagging.
The solution is to only rerender the things that have changed. This works by using listeners; hooks/components that listen for state changes on a specific (or all) fields of a form. Listener hooks behave a lot like useState
.
The built-in inputs like <FormInput />
use this hook and only rerender itself when needed. The useForm
hook, for example, does not cause a rerender when something changes, it only creates a form state manager, which others can listen to.
Red flashes indicate what parts of the form are being re-rendered.
As you can see, Formik rerenders the whole array each time something changes, while typed-react-form
only rerenders the array when its size changes. Awesome! 😮
I wrote this post because I'm pretty sure I'm not the only one that would be interested in a completely type-checked solution like this.
Check out the documented project here.
I would definitely like some feedback on the documentation. This is the first time writing such an extended documentation and I have to say, it is hard to write easy to understand documentation for others when you know every detail of the project yourself.
Anyway, I hope this was interesting for some of you. Have a great day and thanks for reading! 😄