diff --git a/frontend/.changeset/spicy-kings-attack.md b/frontend/.changeset/spicy-kings-attack.md new file mode 100644 index 00000000..11123e39 --- /dev/null +++ b/frontend/.changeset/spicy-kings-attack.md @@ -0,0 +1,5 @@ +--- +'pydantic-forms': minor +--- + +Fix validation error not resetting when form field has change diff --git a/frontend/packages/pydantic-forms/src/core/PydanticFormHandler.tsx b/frontend/packages/pydantic-forms/src/core/PydanticFormHandler.tsx index e6836ed9..5b179b69 100644 --- a/frontend/packages/pydantic-forms/src/core/PydanticFormHandler.tsx +++ b/frontend/packages/pydantic-forms/src/core/PydanticFormHandler.tsx @@ -68,6 +68,7 @@ export const PydanticFormHandler = ({ isLoading, pydanticFormSchema, defaultValues, + handleRemoveValidationError, } = usePydanticForm( formKey, config, @@ -117,6 +118,7 @@ export const PydanticFormHandler = ({ onPrevious={onPrevious} pydanticFormSchema={pydanticFormSchema} title={title} + handleRemoveValidationError={handleRemoveValidationError} /> ); diff --git a/frontend/packages/pydantic-forms/src/core/ReactHookForm.tsx b/frontend/packages/pydantic-forms/src/core/ReactHookForm.tsx index 6e41ec13..0fb10499 100644 --- a/frontend/packages/pydantic-forms/src/core/ReactHookForm.tsx +++ b/frontend/packages/pydantic-forms/src/core/ReactHookForm.tsx @@ -5,7 +5,7 @@ * * Here we define the outline of the form */ -import React from 'react'; +import React, { useEffect } from 'react'; import type { FieldValues } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form'; @@ -36,6 +36,7 @@ export interface ReactHookFormProps { onPrevious: () => void; pydanticFormSchema?: PydanticFormSchema; title?: string; + handleRemoveValidationError: (location: string) => void; } export const ReactHookForm = ({ @@ -51,6 +52,7 @@ export const ReactHookForm = ({ onPrevious, pydanticFormSchema, title, + handleRemoveValidationError, }: ReactHookFormProps) => { const config = useGetConfig(); const t = useTranslations('renderForm'); @@ -73,6 +75,13 @@ export const ReactHookForm = ({ values: initialValues || defaultValues, }); + useEffect(() => { + const reactHookFormWatch = reactHookForm.watch( + (_, { name }) => name && handleRemoveValidationError(name), + ); + return reactHookFormWatch.unsubscribe; + }, [handleRemoveValidationError, reactHookForm]); + if (apiError) { console.error('API Error:', apiError); return ErrorComponent; diff --git a/frontend/packages/pydantic-forms/src/core/WrapFieldElement.tsx b/frontend/packages/pydantic-forms/src/core/WrapFieldElement.tsx index ea5c2602..3fa9c11a 100644 --- a/frontend/packages/pydantic-forms/src/core/WrapFieldElement.tsx +++ b/frontend/packages/pydantic-forms/src/core/WrapFieldElement.tsx @@ -2,7 +2,21 @@ import React from 'react'; import { Controller, useFormContext } from 'react-hook-form'; import { FieldWrap } from '@/components/fields'; -import type { PydanticFormControlledElement, PydanticFormField } from '@/types'; +import { useGetValidationErrors } from '@/core/hooks'; +import { + PydanticFormControlledElement, + PydanticFormField, + PydanticFormValidationErrorDetails, +} from '@/types'; + +const getValidationErrorMsg = ( + errorResponse: PydanticFormValidationErrorDetails | null, + path: string, +): string | undefined => { + return errorResponse?.source?.find( + (err) => err.loc.map(String).join('.') === path, + )?.msg; +}; export const WrapFieldElement = ({ PydanticFormControlledElement, @@ -14,6 +28,8 @@ export const WrapFieldElement = ({ extraTriggerFields?: string[]; }) => { const { control, trigger } = useFormContext(); + const validationErrorDetails = useGetValidationErrors(); + return ( void; } +const removeValidationErrorByLoc = ( + validationErrors: PydanticFormValidationErrorDetails | null, + locToRemove: string, +): PydanticFormValidationErrorDetails | null => { + if (!validationErrors) return null; + + const newSource = validationErrors.source.filter((err) => { + const locPath = err.loc.join('.'); // e.g. "contact_persons.0.email" + return locPath !== locToRemove; + }); + + const [topKey] = locToRemove.split('.'); // e.g. "contact_persons" + const newMapped = { ...validationErrors.mapped }; + + if (topKey && newMapped[topKey]) { + delete newMapped[topKey]; + } + + return { + ...validationErrors, + source: newSource, + mapped: newMapped, + }; +}; + export function usePydanticForm( formKey: string, config: PydanticFormConfig, @@ -131,6 +157,13 @@ export function usePydanticForm( // eslint-disable-next-line react-hooks/exhaustive-deps }, [apiResponse]); + const handleRemoveValidationError = (location: string) => { + setValidationErrorsDetails((prev) => { + const updatedErrors = removeValidationErrorByLoc(prev, location); + return updatedErrors; + }); + }; + return { validationErrorsDetails, apiError, @@ -140,5 +173,6 @@ export function usePydanticForm( pydanticFormSchema, defaultValues, isSending, + handleRemoveValidationError, }; }