When we use onSubmit, the default behaviour is to instant scroll and focus invalid element. Sometimes, if you have fixed headers, the input becomes hidden after scroll.
The following function will:
- Select the first errored input
- Smoothly scroll to selected input
- Apply offset from top
- Set focus, when input becomes visible
// Declare a window variable to get only first invocation of 'onInvalid' from 'form'
declare global {
interface Window {
scrolled?: boolean;
}
}
export function scrollToElement(
element: EventTarget & HTMLFormElement,
offset: number = 250
) {
// It uses offset to check if its visible below header
function isFullVisible(element: EventTarget & HTMLFormElement, offset: number = 0) {
const elementRect = element.getBoundingClientRect();
const windowHeight =
(window.innerHeight || document.documentElement.clientHeight);
const isTopVisible = elementRect.top >= offset && elementRect.top < windowHeight;
const isBottomVisible =
elementRect.bottom >= offset && elementRect.bottom <= windowHeight;
const fullVisible = isTopVisible && isBottomVisible;
return fullVisible
}
function run(element: EventTarget & HTMLFormElement, offset: number = 250) {
const elementPosition =
element.getBoundingClientRect().top + window.scrollY;
const alreadyRunt = window.scrolled;
if (alreadyRunt) {
return;
} else {
window.scrolled = true;
// Time to guarantee that all invocations of 'onInvalid' was ended
setTimeout(() => delete window["scrolled"], 300);
}
if (isFullVisible(element, offset)) {
element?.focus();
return;
}
window.scroll({
// This scroll is not always accurate
// -1 is to force a scroll if by chance it is not visible for less than 1px
top: elementPosition - offset - 1,
behavior: "smooth",
});
window.addEventListener("scroll", function scrollHandler() {
// 0 offset to immediate focus on user view, and not when scroll ended
const fullVisible = isFullVisible(element, 0);
if (fullVisible) {
window.removeEventListener("scroll", scrollHandler);
element?.focus();
}
});
}
return run(element, offset);
}
Then just use it
<form
onSubmit={(e) => {
// ...
}}
onInvalid={(e) => {
e.preventDefault()
const element = e.target as EventTarget & HTMLFormElement
scrollToElement(element, 250) // 250 is header height + gap
}}
>
...
This post was created with the aim of helping people through the search, and registering this snippet for my future uses.
I hope you found this, and it helped you. :D
Top comments (0)