modular-forms icon indicating copy to clipboard operation
modular-forms copied to clipboard

Enhance FieldStore to Return Multiple Errors per Field

Open Toeler opened this issue 1 year ago • 2 comments

I'm currently working with Valibot and Modular Forms, where the Field component's children prop provides a FieldStore. This store includes an error: string property. However, this implementation seems limited as a field might have multiple validation errors, but FieldStore only returns the first error encountered.

This limitation requires additional logic on my end. To display all errors, I need to call getValues and then use safeParse from Valibot, which is not ideal for development experience. I initially thought getErrors could be a solution, but it also returns only the first error for each field.

For an improved DX, I propose enhancing FieldStore to include an errors: string[] | undefined property. This change would allow developers to easily access and display all relevant errors for a field.

Could this enhancement be considered for a future update?

Toeler avatar Jan 11 '24 22:01 Toeler

This behavior is intentional and probably expected in most cases. However, I agree that this should be configurable or we should switch to your approach in the long run.

For now, there is a workaround. You can copy the code from the Valibot adpater and set abortPipeEarly to false. You also need to change the logic so that fields with the same path are not overwritten, but the strings are concatenated.

fabian-hiller avatar Jan 12 '24 01:01 fabian-hiller

Thank you. I didn't consider just rewriting valiForm. Indeed, that gets most of the way there although as you said, you would need to concatenate the errors rather than returning them as an array.

This is a hacked attempt as making use of domain knowledge that the string is actually a string[] | undefined.

import { $, implicit$FirstArg, type QRL } from '@builder.io/qwik';
import type {
	FieldValues,
	FormErrors,
	MaybeFunction,
	PartialValues,
	ValidateForm,
} from '@modular-forms/qwik';
import type { BaseSchema, BaseSchemaAsync } from 'valibot';

/**
 * See {@link valiFormWithErrors$}
 */
export function valiFormWithErrorsQrl<TFieldValues extends FieldValues>(
	schema: QRL<MaybeFunction<BaseSchema<TFieldValues, any> | BaseSchemaAsync<TFieldValues, any>>>,
): QRL<ValidateForm<TFieldValues>> {
	return $(async (values: PartialValues<TFieldValues>) => {
		const resolvedSchema = await schema.resolve();
		const result = await (typeof resolvedSchema === 'function'
			? resolvedSchema()
			: resolvedSchema
		)._parse(values, { abortPipeEarly: false });
		console.log('vali', result);
		return result.issues
			? result.issues.reduce<FormErrors<TFieldValues>>((errors, issue) => {
					const key = issue.path!.map(({ key }) => key).join('.') as keyof FormErrors<TFieldValues>;
					const fieldErrors: string[] = (errors[key] as unknown as string[] | undefined) ?? [];
					fieldErrors.push(issue.message);

					errors[key] = fieldErrors as unknown as string; // Intentionally hide an array as a string

					console.log(key, 'issues', errors);
					return errors;
			  }, {})
			: ({} as FormErrors<TFieldValues>);
	});
}

/**
 * Creates a validation functions that parses the Valibot schema of a form.
 *
 * @param schema A Valibot schema.
 *
 * @returns A validation function.
 */
export const valiFormWithErrors$ = implicit$FirstArg(valiFormWithErrorsQrl);

It works, but obviously isn't something worth proceeding with.

My use case is a user registration form with an Email and Password inputs. The email field doesn't really matter, as that just has a single validator. But the password field has a list of constraints, and under the field it shows these constraints and as the user types, each constraint ticks as it passes.

The two requirements is:

  1. Like #116 it would be good to validate each of these at different points (email on blur, but password on input)
  2. This issue, whereby a field reports all of the errors so that I can compare those against the initial constraints that I calculate (by running a validation against the empty input and getting all of the issues)

I will continue to watch this space for cleaner implementations, thanks.

Toeler avatar Jan 12 '24 04:01 Toeler