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.
Why?
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:
- A form to create questionnaires, which is a list of questions, which must be able to contain more than 30 questions. For each list item, 5 inputs would be created. 😨
- A form to create a post.
- A form to create an event.
- A form to register and login.
- The list keeps on going...
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!
Development
When starting this project, there were a few things I wanted to make sure to include:
- Type checking!
- Fast dynamic array fields
- Validation using yup
- Keeping it simple
Challenge 1: Type checking & nested fields
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! 😀
Challenge 2: Minimal rerenders & fast array fields
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! 😮
Final thoughts
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.
CodeStix / typed-react-form
A fast, completely type-checked React form state manager. Supports nested objects/arrays.
Typed React form
A fast, completely type-checked React form state manager, focussed on typescript integration and minimal rerenders. Featuring:
- Date, number, text inputs, checkboxes, radio buttons, textareas, selects
- Object fields
- Array fields
- Validation and yup integration
- Togglable fields
All of this while keeping type-checking!
Documentation
You can find documentation and information on how to get started on the GitHub wiki page.
You can see an example form working in action here
Javascript/typescript React
This library is optimized (and built from the group up) for React with typescript, but it also works with with vanilla React, without enforced type checking.
Contributing
Contributions are always welcome!
To begin developing yourself, clone this repo, and do the following:
-
/
: contains the library source code. Useyarn start
ornpm run start
to watch the files and build each time something changes. This way the testing application gets updated each time. -
example/
…
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! 😄
Top comments (0)