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;