Skip to content

Commit 6619ec8

Browse files
committed
fix(manager): fix submitting
- fields meta attributes corresponds to form state - submit fails on resolve, not on catch - submit data are restarted on submitting
1 parent 85ebdd1 commit 6619ec8

File tree

2 files changed

+140
-37
lines changed

2 files changed

+140
-37
lines changed

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

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1843,7 +1843,7 @@ describe('managerApi', () => {
18431843
it('calls async submit', async () => {
18441844
jest.useFakeTimers();
18451845

1846-
const onSubmit = jest.fn().mockImplementation(() => new Promise((res) => setTimeout(() => res('ok'), 1000)));
1846+
const onSubmit = jest.fn().mockImplementation(() => new Promise((res) => setTimeout(() => res(), 1000)));
18471847
const render = jest.fn();
18481848

18491849
const managerApi = createManagerApi({ onSubmit });
@@ -1870,12 +1870,43 @@ describe('managerApi', () => {
18701870
expect(managerApi().hasSubmitErrors).toEqual(false);
18711871
});
18721872

1873+
it('calls async submit - submit fails on catch (i.e. network error), return to normal', async () => {
1874+
jest.useFakeTimers();
1875+
1876+
const onSubmit = jest.fn().mockImplementation(() => new Promise((res, rej) => setTimeout(() => rej(), 1000)));
1877+
const render = jest.fn();
1878+
1879+
const managerApi = createManagerApi({ onSubmit });
1880+
1881+
managerApi().registerField({ name: 'field', internalId: '1', render });
1882+
1883+
expect(managerApi().submitting).toEqual(false);
1884+
expect(render).not.toHaveBeenCalled();
1885+
1886+
managerApi().submit();
1887+
1888+
expect(render).toHaveBeenCalled();
1889+
render.mockClear();
1890+
expect(managerApi().submitting).toEqual(true);
1891+
1892+
await jest.runAllTimers();
1893+
await jest.runAllTimers(); // catch needs to be run 2x
1894+
1895+
expect(render).toHaveBeenCalled();
1896+
expect(managerApi().submitting).toEqual(false);
1897+
expect(managerApi().submitError).toEqual(undefined);
1898+
expect(managerApi().submitFailed).toEqual(false);
1899+
expect(managerApi().submitSucceeded).toEqual(true);
1900+
expect(managerApi().submitErrors).toEqual(undefined);
1901+
expect(managerApi().hasSubmitErrors).toEqual(false);
1902+
});
1903+
18731904
it('calls async submit - failed', async () => {
18741905
jest.useFakeTimers();
18751906

18761907
const error = 'some evil error';
18771908

1878-
const onSubmit = jest.fn().mockImplementation(() => new Promise((res, rej) => setTimeout(() => rej(error), 100)));
1909+
const onSubmit = jest.fn().mockImplementation(() => new Promise((res) => setTimeout(() => res(error), 100)));
18791910
const render = jest.fn();
18801911

18811912
const managerApi = createManagerApi({ onSubmit });
@@ -1893,7 +1924,6 @@ describe('managerApi', () => {
18931924
expect(managerApi().submitting).toEqual(true);
18941925

18951926
await jest.runAllTimers();
1896-
await jest.runAllTimers(); // for some reason, catch branch is not triggerd on first run
18971927

18981928
expect(render).toHaveBeenCalled();
18991929
expect(managerApi().submitError).toEqual(undefined);
@@ -1909,7 +1939,7 @@ describe('managerApi', () => {
19091939

19101940
const error = { [FORM_ERROR]: 'some evil error' };
19111941

1912-
const onSubmit = jest.fn().mockImplementation(() => new Promise((res, rej) => setTimeout(() => rej(error), 100)));
1942+
const onSubmit = jest.fn().mockImplementation(() => new Promise((res) => setTimeout(() => res(error), 100)));
19131943
const render = jest.fn();
19141944

19151945
const managerApi = createManagerApi({ onSubmit });
@@ -1927,7 +1957,6 @@ describe('managerApi', () => {
19271957
expect(managerApi().submitting).toEqual(true);
19281958

19291959
await jest.runAllTimers();
1930-
await jest.runAllTimers(); // for some reason, catch branch is not triggerd on first run
19311960

19321961
expect(render).toHaveBeenCalled();
19331962
expect(managerApi().submitError).toEqual('some evil error');
@@ -1937,6 +1966,45 @@ describe('managerApi', () => {
19371966
expect(managerApi().submitErrors).toEqual(error);
19381967
expect(managerApi().hasSubmitErrors).toEqual(true);
19391968
});
1969+
1970+
it('calls async submit - failed - set async error on field', async () => {
1971+
jest.useFakeTimers();
1972+
1973+
const error = { nested: { field: 'some evil error' } };
1974+
1975+
const onSubmit = jest.fn().mockImplementation(() => new Promise((res) => setTimeout(() => res(error), 100)));
1976+
const render = jest.fn();
1977+
1978+
const managerApi = createManagerApi({ onSubmit });
1979+
1980+
managerApi().registerField({ name: 'nested.field', internalId: '1', render });
1981+
1982+
expect(managerApi().submitting).toEqual(false);
1983+
expect(render).not.toHaveBeenCalled();
1984+
1985+
managerApi().submit();
1986+
1987+
expect(render).toHaveBeenCalled();
1988+
render.mockClear();
1989+
1990+
expect(managerApi().getFieldState('nested.field').submitting).toEqual(true);
1991+
expect(managerApi().submitting).toEqual(true);
1992+
1993+
await jest.runAllTimers();
1994+
1995+
expect(render).toHaveBeenCalled();
1996+
expect(managerApi().submitError).toEqual(undefined);
1997+
expect(managerApi().submitting).toEqual(false);
1998+
expect(managerApi().submitFailed).toEqual(true);
1999+
expect(managerApi().submitSucceeded).toEqual(false);
2000+
expect(managerApi().submitErrors).toEqual(error);
2001+
expect(managerApi().hasSubmitErrors).toEqual(true);
2002+
2003+
expect(managerApi().getFieldState('nested.field').submitError).toEqual('some evil error');
2004+
expect(managerApi().getFieldState('nested.field').submitting).toEqual(false);
2005+
expect(managerApi().getFieldState('nested.field').submitFailed).toEqual(true);
2006+
expect(managerApi().getFieldState('nested.field').submitSucceeded).toEqual(false);
2007+
});
19402008
});
19412009

19422010
describe('warning validation', () => {

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

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ const createManagerApi: CreateManagerApi = ({
247247
let isSilent = false;
248248
let silentRender: string[] = [];
249249
let runningValidators = 0;
250+
let flatSubmitErrors: AnyObject = {};
250251

251252
function updateRunningValidators(increment: number): void {
252253
runningValidators = Math.max(runningValidators + increment, 0);
@@ -611,55 +612,89 @@ const createManagerApi: CreateManagerApi = ({
611612
const result = config.onSubmit(state.values);
612613

613614
if (isPromise(result)) {
614-
updateSubmitting(true);
615+
setSubmitting();
615616
const render = prepareRerender();
616617

617618
result
618-
.then(() => {
619-
state.submitErrors = undefined;
620-
state.hasSubmitErrors = false;
621-
state.submitFailed = false;
622-
state.submitSucceeded = true;
623-
state.submitting = false;
624-
state.submitError = undefined;
625-
619+
.then((errors: unknown) => {
620+
handleSubmitError(errors);
621+
updateFieldSubmitMeta();
626622
render();
627623

628624
runAfterSubmit();
629625
})
630-
.catch((error: unknown) => {
631-
state.submitErrors = error as AnyObject;
632-
state.hasSubmitErrors = true;
633-
state.submitFailed = true;
634-
state.submitSucceeded = false;
635-
state.submitting = false;
636-
state.submitError = state.submitErrors?.[FORM_ERROR];
637-
626+
.catch(() => {
627+
handleSubmitError();
628+
updateFieldSubmitMeta();
638629
render();
639630
});
640631
} else {
641632
const render = prepareRerender();
642633

643-
if (result) {
644-
state.submitErrors = result;
645-
state.hasSubmitErrors = true;
646-
state.submitFailed = true;
647-
state.submitSucceeded = false;
648-
state.submitError = state.submitErrors?.[FORM_ERROR];
649-
} else {
650-
state.submitErrors = undefined;
651-
state.hasSubmitErrors = false;
652-
state.submitFailed = false;
653-
state.submitSucceeded = true;
654-
state.submitError = undefined;
655-
}
634+
handleSubmitError(result);
635+
updateFieldSubmitMeta();
656636

657637
render();
658638

659639
runAfterSubmit();
660640
}
661641
}
662642

643+
function updateFieldSubmitMeta(): void {
644+
traverseObject(state.fieldListeners, (field, name) => {
645+
if (field.state) {
646+
setFieldState(
647+
name,
648+
(prevState) => ({
649+
...prevState,
650+
meta: {
651+
...prevState.meta,
652+
submitFailed: state.submitFailed,
653+
submitSucceeded: state.submitSucceeded,
654+
submitError: flatSubmitErrors[name],
655+
submitting: state.submitting
656+
}
657+
}),
658+
true
659+
);
660+
}
661+
});
662+
}
663+
664+
function handleSubmitError(errors?: any): void {
665+
state.submitting = false;
666+
if (errors) {
667+
state.submitErrors = errors;
668+
state.hasSubmitErrors = true;
669+
state.submitFailed = true;
670+
state.submitSucceeded = false;
671+
state.submitError = state.submitErrors?.[FORM_ERROR];
672+
flatSubmitErrors = flatObject(errors);
673+
} else {
674+
state.submitErrors = undefined;
675+
state.hasSubmitErrors = false;
676+
state.submitFailed = false;
677+
state.submitSucceeded = true;
678+
state.submitError = undefined;
679+
flatSubmitErrors = {};
680+
}
681+
}
682+
683+
function setSubmitting() {
684+
const render = prepareRerender();
685+
686+
state.submitErrors = undefined;
687+
state.hasSubmitErrors = false;
688+
state.submitFailed = false;
689+
state.submitSucceeded = false;
690+
state.submitting = true;
691+
state.submitError = undefined;
692+
693+
updateFieldSubmitMeta();
694+
695+
render();
696+
}
697+
663698
function runAfterSubmit() {
664699
state.registeredFields.forEach((name) =>
665700
traverseObject(state.fieldListeners[name].fields, (field) => {
@@ -764,11 +799,11 @@ const createManagerApi: CreateManagerApi = ({
764799
});
765800
}
766801

767-
function setFieldState(name: string, mutateState: (prevState: FieldState) => FieldState): void {
802+
function setFieldState(name: string, mutateState: (prevState: FieldState) => FieldState, dryRun = false): void {
768803
if (state.fieldListeners[name]) {
769804
const newState = mutateState(state.fieldListeners[name].state);
770805
state.fieldListeners[name].state = newState;
771-
Object.values(state.fieldListeners[name].fields).forEach(({ render }) => render());
806+
!dryRun && Object.values(state.fieldListeners[name].fields).forEach(({ render }) => render());
772807
}
773808
}
774809

0 commit comments

Comments
 (0)