‘Save all’ multi-form experience
By: Evan Williams
Tags: JavaScript, Reactjs, React Hook Form, Web Development, React

As discussed in my recently published article on form data loss prevention, my day is all about forms. From big to small, modal experience to page level and everything in between, forms are my bread and butter.

In a similar problem vein as keeping track of dirty state & preventing data loss, we wanted to provide the user a convenience save button to save all segments of the 1003 (mortgage origination form) at once. This was made extremely easy by leveraging our form framework, React Hook Form. If you want to read more on how we leverage RHF, feel free to check out my Medium profile…it’s basically all I’ve talked about for 2020 (for good reason)!

There are several key features & behaviors we were targeting that I’ll discuss below:

Consistent form/API interactions

Error highlighting & messaging

A way to indicate changes, and minimize API noise

Consistent Form & API interactions

One of the biggest drivers for our solution was ensuring we did not have to maintain an extremely brittle Redux action for saving the entire 1003. What this meant to us is putting the control of how the forms interact with the API in the proverbial hands of the components most intimate with the interaction definition: the forms themselves.

Each segment of the 1003 is responsible for fetching the pertinent data models to display to the user, as well as collect and submit said data back to the API. This means each form is effectively encapsulating what it means to be one smaller piece of the bigger puzzle. On mount of the component, like all react hook forms, we make a call to useForm and define a handleSubmit callback. This is the callback leveraged for onSubmit . What it also contains is how to appropriately message to the user that the form has been successfully saved or that an error has occurred. More on this in the next section, though!

What we end up producing for the “Save All” functionality, is a single promise per section. In those promises, we do two things:

On successful resolve of the API, we resolve the ID of the form that was submitted

On rejection, we reject the ID of the form in order to track which sections failed.

Error Handling & Messaging

Leveraging Promise.allSettled , we can effectively determine whether to toast a notification to the user that all segments were saved, if there was a partial save, or if all of them had errors.

Rejections are handled at an individual form level, where the component can appropriately queue a toast message with specific error messaging that is pertinent to the form. This is an important detail because it keeps the coupling of the form’s responsibility inside of the component itself, without the Save All functionality needing to have any label duplication that is hard to maintain.

One amazing aspect of React Hook Form is its ability to tie the form state into the individual element references. This means when you register an input to RHF, it is able to call DOM node methods such as focus when appropriate. Thanks to handleSubmit , if an error occurs within a form due to, say, validation, the user is brought to the element that prevented form sections from submitting.

Additionally, if we’re able to determine a specific field that caused an API error, the forms can perform a setError and get the same focus functionality. Since these are all registered to a single callback invoked by our save button, the execution of these promises & setError callbacks allow us to draw attention to the problem field without much effort on our part, outside the form itself!

Even better, all of this logic needed to exist within the form for the individual section submission, anyway. We’re simply leveraging it to create a better user experience.

Indicate form changes, minimize API noise

One other handy behavior we have from React Hook Form is dirty state. We leverage this for form data loss, but also can leverage it to indicate to the user that sections have been modified (it’s a big form, easy to forget what you’ve answered!). This is handy for enabling/disabling save buttons and allow these indications as a subtle reminder to the user.

Additionally, we can use that same dirty form state to also eliminate unnecessary updates being made to the API when a Save All is performed by the user. What’s the point in pushing data to the API that has not changed, after all?