Merged in fix/loy-514-fix-validation-tracking-in-signup-form (pull request #3445)

fix(LOY-514): Fix validation error tracking in SignupForm

* Fix issue with form submit handling

* Fix

* Remove browser validation

* Add automatic tracking of validatione rrors


Approved-by: Rasmus Langvad
Approved-by: Matilda Landström
This commit is contained in:
Anton Gunnarsson
2026-01-19 08:34:24 +00:00
parent 0c5670823b
commit 66b7af877a
11 changed files with 43 additions and 47 deletions
@@ -146,7 +146,6 @@ export default function ChildInfoSelector({
<div key={index} className={styles.childInfoContainer}>
<div ref={ageSelectRef}>
<Select
isRequired
items={ageList}
name={ageFieldName}
label={ageLabel}
@@ -161,7 +160,6 @@ export default function ChildInfoSelector({
<div ref={bedPrefSelectRef}>
{child.age >= 0 ? (
<Select
isRequired
items={getAvailableBeds(child.age)}
name={bedFieldName}
label={bedLabel}
@@ -85,7 +85,6 @@ export default function CountryCombobox({
className={styles.select}
data-testid={name}
isDisabled={disabled}
isRequired={Boolean(registerOptions?.required)}
isInvalid={fieldState.invalid}
name={name}
onBlur={field.onBlur}
@@ -54,7 +54,6 @@ export default function CountrySelect({
items={items}
label={label}
isDisabled={disabled}
isRequired={Boolean(registerOptions?.required)}
isInvalid={fieldState.invalid}
name={name}
onBlur={field.onBlur}
@@ -127,7 +127,6 @@ export default function DateSelect({
label={labels.day}
name={DateName.day}
onSelectionChange={(key) => setValue(DateName.day, Number(key))}
isRequired
enableFiltering={isDesktop}
isInvalid={fieldState.invalid}
onBlur={field.onBlur}
@@ -142,7 +141,6 @@ export default function DateSelect({
label={labels.month}
name={DateName.month}
onSelectionChange={(key) => setValue(DateName.month, Number(key))}
isRequired
enableFiltering={isDesktop}
isInvalid={fieldState.invalid}
onBlur={field.onBlur}
@@ -156,7 +154,6 @@ export default function DateSelect({
label={labels.year}
name={DateName.year}
onSelectionChange={(key) => setValue(DateName.year, Number(key))}
isRequired
enableFiltering={isDesktop}
isInvalid={fieldState.invalid}
onBlur={field.onBlur}
@@ -78,7 +78,6 @@ export const FormInput = forwardRef<HTMLInputElement, FormInputProps>(
isDisabled={isDisabled}
isReadOnly={readOnly}
isInvalid={fieldState.invalid}
isRequired={!!registerOptions.required}
>
<Input
{...props}
@@ -59,7 +59,6 @@ export const FormTextArea = forwardRef<HTMLTextAreaElement, FormTextAreaProps>(
isDisabled={isDisabled}
isReadOnly={readOnly}
isInvalid={fieldState.invalid}
isRequired={!!registerOptions.required}
>
<TextArea
{...props}
@@ -131,7 +131,6 @@ export default function Phone({
aria-label={ariaLabel}
isDisabled={disabled || registerOptions.disabled}
isInvalid={fieldState.invalid}
isRequired={!!registerOptions?.required}
name={name}
type="tel"
value={phoneNumber}
@@ -89,7 +89,6 @@ const InputComponent = forwardRef(function AriaInputWithLabelComponent(
<AriaInput
{...props}
id={inputId}
required={required}
// Avoid duplicating label text in placeholder when label is positioned above
// Screen readers would announce the label twice (once as label, once as placeholder)
// Only use placeholder if explicitly provided, otherwise use empty string
@@ -140,7 +139,6 @@ const InputComponent = forwardRef(function AriaInputWithLabelComponent(
<AriaInput
{...props}
id={id}
required={required}
// For floating labels, only set placeholder if explicitly provided
// The label itself acts as the placeholder, so we don't want to duplicate it
// This ensures the label only floats when focused or has value
@@ -84,7 +84,6 @@ export const PasswordInput = ({
aria-describedby={describedBy}
isDisabled={field.disabled}
isInvalid={fieldState.invalid}
isRequired={!!registerOptions.required}
name={field.name}
onBlur={field.onBlur}
onChange={field.onChange}
+31
View File
@@ -13,8 +13,11 @@ import {
trackFormAbandonment,
trackFormCompletion,
trackFormInputStarted,
trackFormValidationError,
} from "./form"
import type { FieldErrors } from "react-hook-form"
export function useFormTracking<T extends FieldValues>(
formType: FormType,
subscribe: UseFormSubscribe<T>,
@@ -66,6 +69,13 @@ export function useFormTracking<T extends FieldValues>(
}
}, [formStarted, formType, nameSuffix, formState.isValid])
useEffect(() => {
if (formState.submitCount === 0) return
if (formState.isValid) return
trackErrors(formState.errors, formType, nameSuffix)
}, [formState.submitCount, formState.errors, formType, nameSuffix])
const trackFormSubmit = useCallback(() => {
if (formState.isValid) {
trackFormCompletion(formType, nameSuffix)
@@ -76,3 +86,24 @@ export function useFormTracking<T extends FieldValues>(
trackFormSubmit,
}
}
function trackErrors<T extends FieldValues>(
errors: FieldErrors<T>,
formType: FormType,
nameSuffix: string
) {
const errorKeys = Object.getOwnPropertyNames(errors)
errorKeys.forEach((key) => {
const msg = errors[key]?.message
if (!msg) {
// Handle nested errors
trackErrors(errors[key] as FieldErrors<T>, formType, nameSuffix)
return
}
if (!msg || typeof msg !== "string") return
trackFormValidationError(formType, nameSuffix, msg)
})
}