Skip to content

Commit 7d36576

Browse files
[Form lib] UseField onError listener (#89895) (#90023)
* added callback for listening to field onerror events * added onError component integration test * address tslint issues Co-authored-by: Kibana Machine <[email protected]> Co-authored-by: Kibana Machine <[email protected]>
1 parent ca3fe07 commit 7d36576

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,4 +287,55 @@ describe('<UseField />', () => {
287287
expect(formHook?.getFormData()).toEqual({ name: 'myName' });
288288
});
289289
});
290+
291+
describe('change handlers', () => {
292+
const onError = jest.fn();
293+
294+
beforeEach(() => {
295+
jest.resetAllMocks();
296+
});
297+
298+
const getTestComp = (fieldConfig: FieldConfig) => {
299+
const TestComp = () => {
300+
const { form } = useForm<any>();
301+
302+
return (
303+
<Form form={form}>
304+
<UseField path="name" config={fieldConfig} data-test-subj="myField" onError={onError} />
305+
</Form>
306+
);
307+
};
308+
return TestComp;
309+
};
310+
311+
const setup = (fieldConfig: FieldConfig) => {
312+
return registerTestBed(getTestComp(fieldConfig), {
313+
memoryRouter: { wrapComponent: false },
314+
})() as TestBed;
315+
};
316+
317+
it('calls onError when validation state changes', async () => {
318+
const {
319+
form: { setInputValue },
320+
} = setup({
321+
validations: [
322+
{
323+
validator: ({ value }) => (value === '1' ? undefined : { message: 'oops!' }),
324+
},
325+
],
326+
});
327+
328+
expect(onError).toBeCalledTimes(0);
329+
await act(async () => {
330+
setInputValue('myField', '0');
331+
});
332+
expect(onError).toBeCalledTimes(1);
333+
expect(onError).toBeCalledWith(['oops!']);
334+
await act(async () => {
335+
setInputValue('myField', '1');
336+
});
337+
expect(onError).toBeCalledTimes(2);
338+
expect(onError).toBeCalledWith(null);
339+
});
340+
});
290341
});

src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface Props<T, FormType = FormData, I = T> {
2020
componentProps?: Record<string, any>;
2121
readDefaultValueOnForm?: boolean;
2222
onChange?: (value: I) => void;
23+
onError?: (errors: string[] | null) => void;
2324
children?: (field: FieldHook<T, I>) => JSX.Element | null;
2425
[key: string]: any;
2526
}
@@ -33,6 +34,7 @@ function UseFieldComp<T = unknown, FormType = FormData, I = T>(props: Props<T, F
3334
componentProps,
3435
readDefaultValueOnForm = true,
3536
onChange,
37+
onError,
3638
children,
3739
...rest
3840
} = props;
@@ -62,7 +64,7 @@ function UseFieldComp<T = unknown, FormType = FormData, I = T>(props: Props<T, F
6264
}
6365
}
6466

65-
const field = useField<T, FormType, I>(form, path, fieldConfig, onChange);
67+
const field = useField<T, FormType, I>(form, path, fieldConfig, onChange, onError);
6668

6769
// Children prevails over anything else provided.
6870
if (children) {

src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export const useField = <T, FormType = FormData, I = T>(
2727
form: FormHook<FormType>,
2828
path: string,
2929
config: FieldConfig<T, FormType, I> & InternalFieldConfig<T> = {},
30-
valueChangeListener?: (value: I) => void
30+
valueChangeListener?: (value: I) => void,
31+
errorChangeListener?: (errors: string[] | null) => void
3132
) => {
3233
const {
3334
type = FIELD_TYPES.TEXT,
@@ -596,6 +597,15 @@ export const useField = <T, FormType = FormData, I = T>(
596597
};
597598
}, [onValueChange]);
598599

600+
useEffect(() => {
601+
if (!isMounted.current) {
602+
return;
603+
}
604+
if (errorChangeListener) {
605+
errorChangeListener(errors.length ? errors.map((error) => error.message) : null);
606+
}
607+
}, [errors, errorChangeListener]);
608+
599609
useEffect(() => {
600610
isMounted.current = true;
601611

0 commit comments

Comments
 (0)