erikras.com
HomeAbout

Focus On Errors 🧐

Without a doubt, one of the most common questions I hear from people using my forms libraries is, "How can I focus on the first field that has an error?"

Posted in Coding, 🏁 Final Form
March 19, 2018 - 5 min read
Photo by Erik Rasmussen

Without a doubt, one of the most common questions I hear from people using my forms libraries is:

“How can I focus on the first field that has an error?”

Merely asking this question demonstrates a perceptive level of UX intelligence. Clearly it’s an order of magnitude better to actually take a user to the problem spot rather than just tell them “There’s an error up there somewhere. Scroll up and try to find it!”

Here’s the answer I usually give:

Well, that’s a tough problem, because calling focus() on an input requires having direct access to the DOM instance, and [form library] does not have that; all [form library] does is provide a value and onChange (and other listeners) to you, the developer that actually renders the <input>, and there is no good way for you, who may have a DOM reference to the input, to give that back to [form library] to allow [form library] to call focus() on the DOM element.

🙄 Just typing that practically put me to sleep. Yawn! 😴

Sleepy...zzzz

🤔 Can we do better?

It ocurred to me that, at least in DOM-land (sorry React Native folks!), we have this ancient structure which, like Seinfeld — “What’s the deal with render props?!” — dates back to the early 1990’s, that modern web developers might not know about. Did you know that document.forms[0][0] will give you a reference to the first input on the first form on your page? Or, that if you give your form a name, e.g. <form name="foo">, that you can iterate through an HTMLCollection of its inputs at document.forms.foo? 🤯

So, maybe a higher level form library can gain access to the input DOM elements, as long as they are given the name value that matches the field name the form library knows about?


🥁 🥁 🥁 Introducing, the alliterative…

🏁 Final Form Focus 🧐

🏁 Final Form Focus 🧐 is a decorator for 🏁 Final Form. That means that you just import it and plug it in, and it does all the rest. There is only one possible change in configuration: in the interest of future proofing it and maybe allowing non-DOM platforms, like React Native, to take advantage of it, you may provide a getInputs() function that will give a list of “focusable inputs” (i.e. objects with a name property and a focus() method) that correspond to the fields in your form. For those of us in DOM land, you can omit that parameter and it will default to searching through your document.forms structure and will "just work".

Whenever your form is submitted and the submission fails due to synchronous, asynchronous, or submission validation errors, the first input on the page that has a name prop that matches the path to an error will have focus() called on it.

What does it look like? Well, if you’re using 🏁 React Final Form, you just add the decorator like this:

import React from 'react'
import { Form, Field } from 'react-final-form
import createDecorator from 'final-form-focus'
const focusOnErrors = createDecorator()
...
<Form
onSubmit={submit}
decorators={[ focusOnErrors ]} // <--------- 😎
validate={validate}
render={({ handleSubmit }) =>
<form onSubmit={handleSubmit}>
... inputs here ...
</form>
}
/>
Step 1: Add the decorator to your form. Step 2: There is no Step 2!

Let’s see it in action:

Vanilla JS Code

For those of you that have arrived to this post by googling “how to focus on the input with the first validation error” (Look, ma! I’m SEOing!!), or looking to add this functionality to your own form solution, this is all it takes:

function focusOnFirstError(formName, hasError) {
const form = document.forms[formName];
for (let i = 0; i < form.length; i++) {
if (hasError(form[i].name)) {
form[i].focus();
break;
}
}
}
`hasError()` is a predicate that returns truthy if a field has an error

Conclusion

Almost none of the web forms — or native forms, for that matter — I use on a daily basis are sophisticated enough to bring focus to the first field with an error, and thus introduce friction and frustration into the user experience. Implementing “focus on first error” manually on every form is quite difficult, e.g. keeping track of all the DOM refs, sorting them by which is first on the page, and examining the error structure at submission to determine which one to focus on.

But now, with 🏁 Final Form, there is a pluggable way to get that functionality on every form you write.

❤️ Thanks for reading, and I look forward to your feedback! ❤️

© 2021 Erik Rasmussen