diff --git a/packages/ts/react-crud/src/autoform.tsx b/packages/ts/react-crud/src/autoform.tsx index 17a8847f9..5f59ea7dc 100644 --- a/packages/ts/react-crud/src/autoform.tsx +++ b/packages/ts/react-crud/src/autoform.tsx @@ -12,6 +12,7 @@ import { type ReactElement, useEffect, useMemo, + useRef, useState, } from 'react'; import { AutoFormField, type AutoFormFieldProps, type FieldOptions } from './autoform-field.js'; @@ -278,6 +279,7 @@ export function AutoForm({ const form = useForm(model, { onSubmit: async (formItem) => service.save(formItem), }); + const formErrorRef = useRef(null); const [formError, setFormError] = useState(''); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const modelInfo = useMemo(() => new ModelInfo(model, itemIdProperty), [model]); @@ -294,21 +296,30 @@ export function AutoForm({ } }, [item]); + useEffect(() => { + formErrorRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' }); + }, [formError]); + function handleSubmitError(error: unknown) { if (error instanceof ValidationError) { const nonPropertyErrorMessages = error.errors - .filter((validationError) => !validationError.property) - .map((validationError) => validationError.validatorMessage ?? validationError.message); + .filter((validationError) => !validationError.property || typeof validationError.property === 'string') + .map((validationError) => { + const property = typeof validationError.property === 'string' ? `${validationError.property}: ` : ''; + return `${property}${ + validationError.validatorMessage ? validationError.validatorMessage : validationError.message + }`; + }); if (nonPropertyErrorMessages.length > 0) { setFormError( - <> +
Validation errors:
    {nonPropertyErrorMessages.map((message, index) => (
  • {message}
  • ))}
- , +
, ); } } else if (error instanceof EndpointError) { diff --git a/packages/ts/react-crud/test/autoform.spec.tsx b/packages/ts/react-crud/test/autoform.spec.tsx index aafb79782..554eb7e65 100644 --- a/packages/ts/react-crud/test/autoform.spec.tsx +++ b/packages/ts/react-crud/test/autoform.spec.tsx @@ -429,6 +429,29 @@ describe('@vaadin/hilla-react-crud', () => { expect(result.queryByText('just a message')).to.not.be.null; }); + it('shows error for whole form has string property', async () => { + const service: CrudService & HasTestInfo = createService(personData); + const person = await getItem(service, 1); + // eslint-disable-next-line @typescript-eslint/require-await + service.save = async (_item: Person): Promise => { + const valueError: ValueError = { + property: 'myProp', + message: 'message', + value: person, + validator: { message: 'message', validate: () => false }, + validatorMessage: 'foobar', + }; + throw new ValidationError([valueError]); + }; + + const result = render(); + const form = await FormController.init(user, result.container); + await form.typeInField('First name', 'J'); // to enable the submit button + await form.submit(); + expect(result.queryByText('message')).to.be.null; + expect(result.queryByText('myProp: foobar')).to.not.be.null; + }); + it('shows a predefined error message when the service returns no entity after saving', async () => { const service: CrudService & HasTestInfo = createService(personData); service.save = async (item: Person): Promise => Promise.resolve(undefined);