Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | 13x 238x 238x 238x 238x 238x 238x | import { useCallback, useRef } from "react";
import { FieldErrors, FieldValues, UseFormReturn } from "react-hook-form";
export const useScrollToErrorField = ({
methods,
}: {
methods: UseFormReturn<any, any, undefined>;
}) => {
const formRef = useRef<HTMLFormElement | null>(null);
const getFirstErrorFieldName = useCallback(
(errors: FieldErrors<FieldValues>): string | null => {
for (const key of Object.keys(errors)) {
const error = errors[key as keyof typeof errors];
if (!error) continue;
if (
typeof error === "object" &&
"ref" in error &&
error?.ref &&
"name" in error.ref &&
typeof error.ref.name === "string"
) {
return error.ref.name || key;
}
if (typeof error === "object") {
const nestedError = getFirstErrorFieldName(
error as FieldErrors<FieldValues>,
);
if (nestedError) {
return nestedError;
}
}
}
return null;
},
[],
);
const findFirstInvalidElement = useCallback((): HTMLElement | null => {
if (!formRef.current) return null;
const selectors = [
"[aria-invalid='true']",
"[data-invalid='true']",
"input.Mui-error",
"textarea.Mui-error",
"select.Mui-error",
];
for (const selector of selectors) {
const matches = Array.from(
formRef.current.querySelectorAll(selector),
) as HTMLElement[];
for (const element of matches) {
if (!(element instanceof HTMLElement)) {
continue;
}
if (element.offsetParent !== null) {
return element;
}
const focusableChild = element.querySelector<HTMLElement>(
"input, select, textarea, [tabindex]",
);
if (focusableChild && focusableChild.offsetParent !== null) {
return focusableChild;
}
}
}
return null;
}, []);
const scrollToField = useCallback(
(fieldName: string | null) => {
requestAnimationFrame(() => {
if (fieldName) {
try {
methods.setFocus(fieldName as any, { shouldSelect: true });
} catch (error) {
console.log(`Could not set focus for field: ${fieldName}`);
}
}
const targetElement = findFirstInvalidElement();
if (targetElement) {
targetElement.scrollIntoView({ behavior: "smooth", block: "center" });
if (typeof targetElement.focus === "function") {
targetElement.focus({ preventScroll: true });
}
}
});
},
[findFirstInvalidElement, methods],
);
const handleValidationError = useCallback(
(errors: FieldErrors<FieldValues>) => {
const fieldName = getFirstErrorFieldName(errors);
scrollToField(fieldName ?? null);
},
[getFirstErrorFieldName, scrollToField],
);
return {
formRef,
scrollToField,
getFirstErrorFieldName,
handleValidationError,
};
};
|