diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx index 36841f8b1d521e..4945b7a059e8c0 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx @@ -287,4 +287,55 @@ describe('', () => { expect(formHook?.getFormData()).toEqual({ name: 'myName' }); }); }); + + describe('change handlers', () => { + const onError = jest.fn(); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + const getTestComp = (fieldConfig: FieldConfig) => { + const TestComp = () => { + const { form } = useForm(); + + return ( +
+ + + ); + }; + return TestComp; + }; + + const setup = (fieldConfig: FieldConfig) => { + return registerTestBed(getTestComp(fieldConfig), { + memoryRouter: { wrapComponent: false }, + })() as TestBed; + }; + + it('calls onError when validation state changes', async () => { + const { + form: { setInputValue }, + } = setup({ + validations: [ + { + validator: ({ value }) => (value === '1' ? undefined : { message: 'oops!' }), + }, + ], + }); + + expect(onError).toBeCalledTimes(0); + await act(async () => { + setInputValue('myField', '0'); + }); + expect(onError).toBeCalledTimes(1); + expect(onError).toBeCalledWith(['oops!']); + await act(async () => { + setInputValue('myField', '1'); + }); + expect(onError).toBeCalledTimes(2); + expect(onError).toBeCalledWith(null); + }); + }); }); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx index 94c2bc42d28558..cc79ed24b5d0cb 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx @@ -20,6 +20,7 @@ export interface Props { componentProps?: Record; readDefaultValueOnForm?: boolean; onChange?: (value: I) => void; + onError?: (errors: string[] | null) => void; children?: (field: FieldHook) => JSX.Element | null; [key: string]: any; } @@ -33,6 +34,7 @@ function UseFieldComp(props: Props(props: Props(form, path, fieldConfig, onChange); + const field = useField(form, path, fieldConfig, onChange, onError); // Children prevails over anything else provided. if (children) { diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts index c396f223e97fde..db7b0b2820a47d 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts @@ -27,7 +27,8 @@ export const useField = ( form: FormHook, path: string, config: FieldConfig & InternalFieldConfig = {}, - valueChangeListener?: (value: I) => void + valueChangeListener?: (value: I) => void, + errorChangeListener?: (errors: string[] | null) => void ) => { const { type = FIELD_TYPES.TEXT, @@ -596,6 +597,15 @@ export const useField = ( }; }, [onValueChange]); + useEffect(() => { + if (!isMounted.current) { + return; + } + if (errorChangeListener) { + errorChangeListener(errors.length ? errors.map((error) => error.message) : null); + } + }, [errors, errorChangeListener]); + useEffect(() => { isMounted.current = true;