Skip to content

Commit 8b7a2de

Browse files
committed
Allow to set name to find error in the right form element
1 parent 59a6a87 commit 8b7a2de

File tree

6 files changed

+84
-16
lines changed

6 files changed

+84
-16
lines changed

packages/form-state-manager/demo/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const App = () => {
1616
<FormStateManager onSubmit={console.log}>
1717
{({ handleSubmit, ...state }) => {
1818
return (
19-
<form onSubmit={handleSubmit}>
19+
<form onSubmit={handleSubmit} name="field-2">
2020
<h1>There will be children</h1>
2121
<TextField initialValue="foo" autocomplete="off" validate={asyncValidator} label="Field 2" name="field-2-23" id="field-2" type="text" />
2222
<TextField autocomplete="off" validate={asyncValidator} label="Field 2" name="field-2" id="field-2" type="text" />

packages/form-state-manager/src/tests/utils/focus-error.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,31 @@ describe('focusError', () => {
1818

1919
expect(listener).toHaveBeenCalled();
2020
});
21+
22+
it('focus first error element in the right form', async () => {
23+
document.body.innerHTML = `<form name="not-error">
24+
<input name="field-1" />
25+
<input id="not-error" name="this-has-error" />
26+
<input name="field-3" />
27+
</form>
28+
<form name="this-has-error">
29+
<input name="field-1" />
30+
<input id="this-has-error" name="this-has-error" />
31+
<input name="field-3" />
32+
</form>`;
33+
34+
const listenerNotError = jest.fn();
35+
const listenerError = jest.fn();
36+
37+
document.querySelector('#not-error').addEventListener('focus', listenerNotError);
38+
document.querySelector('#this-has-error').addEventListener('focus', listenerError);
39+
40+
expect(listenerNotError).not.toHaveBeenCalled();
41+
expect(listenerError).not.toHaveBeenCalled();
42+
43+
focusError({ 'field-3': 'error', 'this-has-error': 'error' }, 'this-has-error');
44+
45+
expect(listenerNotError).not.toHaveBeenCalled();
46+
expect(listenerError).toHaveBeenCalled();
47+
});
2148
});

packages/form-state-manager/src/tests/utils/manager-api.test.js

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ describe('managerApi', () => {
595595

596596
it('onsubmit receives an error', () => {
597597
const focusErrorSpy = jest.spyOn(focusError, 'default');
598-
const error = 'some-error';
598+
const error = { field: 'some-error' };
599599
const onSubmit = jest.fn().mockImplementation(() => error);
600600
const managerApi = createManagerApi({ onSubmit });
601601
const { registerField } = managerApi();
@@ -615,7 +615,23 @@ describe('managerApi', () => {
615615
expect(managerApi().submitErrors).toEqual(error);
616616
expect(managerApi().hasSubmitErrors).toEqual(true);
617617
expect(managerApi().hasValidationErrors).toEqual(false);
618-
expect(focusErrorSpy).toHaveBeenCalled();
618+
expect(focusErrorSpy).toHaveBeenCalledWith({ field: 'some-error' }, undefined);
619+
});
620+
621+
it('onsubmit receives an error - named form', () => {
622+
const name = 'i-am-form';
623+
const focusErrorSpy = jest.spyOn(focusError, 'default');
624+
const error = { field: 'some-error' };
625+
const onSubmit = jest.fn().mockImplementation(() => error);
626+
const managerApi = createManagerApi({ onSubmit, name });
627+
const { registerField } = managerApi();
628+
629+
const render = jest.fn();
630+
registerField({ name: 'field', render });
631+
632+
managerApi().handleSubmit();
633+
634+
expect(focusErrorSpy).toHaveBeenCalledWith({ field: 'some-error' }, name);
619635
});
620636

621637
it('onsubmit receives an error - form level', () => {
@@ -2084,7 +2100,7 @@ describe('managerApi', () => {
20842100
expect(managerApi().submitSucceeded).toEqual(false);
20852101
expect(managerApi().submitErrors).toEqual(error);
20862102
expect(managerApi().hasSubmitErrors).toEqual(true);
2087-
expect(focusErrorSpy).toHaveBeenCalled();
2103+
expect(focusErrorSpy).toHaveBeenCalledWith({ 'nested.field': 'some evil error' }, undefined);
20882104

20892105
expect(managerApi().getFieldState('nested.field').submitError).toEqual('some evil error');
20902106
expect(managerApi().getFieldState('nested.field').submitting).toEqual(false);
@@ -2212,12 +2228,28 @@ describe('managerApi', () => {
22122228

22132229
managerApi().handleSubmit();
22142230

2215-
expect(focusErrorSpy).toHaveBeenCalled();
2231+
expect(focusErrorSpy).toHaveBeenCalledWith({ field: 'error' }, undefined);
22162232
expect(managerApi().getFieldState('field').touched).toEqual(true);
22172233
expect(managerApi().getFieldState('field').touched).toEqual(true);
22182234

22192235
expect(onSubmit).not.toHaveBeenCalled();
22202236
});
2237+
2238+
it('should not call submit action when invalid + all fields are touched - named field', () => {
2239+
const onSubmit = jest.fn();
2240+
const name = 'form-name';
2241+
2242+
const managerApi = createManagerApi({ onSubmit, name });
2243+
2244+
managerApi().registerField({ name: 'field', validate: () => 'error', render: jest.fn(), internalId: 1 });
2245+
managerApi().registerField({ name: 'field2', render: jest.fn(), internalId: 1 });
2246+
2247+
expect(focusErrorSpy).not.toHaveBeenCalled();
2248+
2249+
managerApi().handleSubmit();
2250+
2251+
expect(focusErrorSpy).toHaveBeenCalledWith({ field: 'error' }, name);
2252+
});
22212253
});
22222254

22232255
it('set form dirty and pristine according to fields', () => {

packages/form-state-manager/src/types/manager-api.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ export interface CreateManagerApiConfig {
201201
debug?: Debug;
202202
keepDirtyOnReinitialize?: boolean;
203203
destroyOnUnregister?: boolean;
204+
name?: string;
204205
}
205206

206207
declare type CreateManagerApi = (CreateManagerApiConfig: CreateManagerApiConfig) => ManagerApi;

packages/form-state-manager/src/utils/focus-error.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import AnyObject from '../types/any-object';
22

3-
const focusError = (errors: AnyObject) => {
3+
const justFocusable = (el: any) => el?.focus;
4+
5+
const focusError = (errors: AnyObject, name?: string): void => {
46
if (document) {
5-
const allInputs = Array.from(document.forms)
6-
.reduce((acc: any, curr: any) => [...acc, ...curr.elements], [])
7-
.filter((el: HTMLElement) => el.focus);
7+
const formInputs = name && document.forms[name as any]?.elements;
8+
9+
const allInputs = formInputs
10+
? Array.from(formInputs).filter(justFocusable)
11+
: Array.from(document.forms)
12+
.reduce((acc: any, curr: any) => [...acc, ...curr.elements], [])
13+
.filter(justFocusable);
814

915
const errorKeys = Object.keys(errors);
1016

packages/form-state-manager/src/utils/manager-api.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,8 @@ const createManagerApi: CreateManagerApi = ({
194194
initialValues,
195195
debug,
196196
keepDirtyOnReinitialize,
197-
destroyOnUnregister
197+
destroyOnUnregister,
198+
name
198199
}) => {
199200
const config: CreateManagerApiConfig = {
200201
onSubmit,
@@ -204,7 +205,8 @@ const createManagerApi: CreateManagerApi = ({
204205
subscription,
205206
debug,
206207
keepDirtyOnReinitialize,
207-
destroyOnUnregister
208+
destroyOnUnregister,
209+
name
208210
};
209211

210212
let state: ManagerState = {
@@ -265,8 +267,8 @@ const createManagerApi: CreateManagerApi = ({
265267

266268
const managerApi: ManagerApi = () => state;
267269

268-
function setConfig(attribute: keyof CreateManagerApiConfig, value: any) {
269-
config[attribute] = value;
270+
function setConfig(attribute: keyof CreateManagerApiConfig, value: CreateManagerApiConfig[keyof CreateManagerApiConfig]) {
271+
(config as AnyObject)[attribute] = value;
270272
}
271273

272274
function isValidationPaused() {
@@ -612,7 +614,7 @@ const createManagerApi: CreateManagerApi = ({
612614
}));
613615
});
614616

615-
focusError(state.errors);
617+
focusError(state.errors, config.name);
616618

617619
return;
618620
}
@@ -639,7 +641,7 @@ const createManagerApi: CreateManagerApi = ({
639641
handleSubmitError(errors);
640642
updateFieldSubmitMeta();
641643
render();
642-
focusError(flatSubmitErrors);
644+
focusError(flatSubmitErrors, config.name);
643645

644646
runAfterSubmit();
645647
})
@@ -656,7 +658,7 @@ const createManagerApi: CreateManagerApi = ({
656658

657659
render();
658660

659-
focusError(flatSubmitErrors);
661+
focusError(flatSubmitErrors, config.name);
660662

661663
runAfterSubmit();
662664
}

0 commit comments

Comments
 (0)