Skip to content

Commit 24cd6fa

Browse files
authored
Merge pull request #822 from rvsia/stateAsyncSubmit
feat(manager): implement async onSubmit
2 parents a262d2d + 5a73a71 commit 24cd6fa

File tree

4 files changed

+207
-2
lines changed

4 files changed

+207
-2
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const FORM_ERROR = 'data-driven-forms/FORM_ERROR';
2+
3+
export default FORM_ERROR;

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

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import createManagerApi, { initialMeta, flatObject } from '../../utils/manager-api';
2+
import FORM_ERROR from '../../files/form-error';
23

34
describe('managerApi', () => {
45
it('should create managerApi getter', () => {
@@ -570,6 +571,55 @@ describe('managerApi', () => {
570571
});
571572
managerApi().handleSubmit({ preventDefault: jest.fn() });
572573
expect(onSubmit).toHaveBeenCalledWith(expectedValues);
574+
575+
expect(managerApi().submitting).toEqual(false);
576+
expect(managerApi().submitError).toEqual(undefined);
577+
expect(managerApi().submitFailed).toEqual(false);
578+
expect(managerApi().submitSucceeded).toEqual(true);
579+
});
580+
581+
it('onsubmit receives an error', () => {
582+
const error = 'some-error';
583+
const onSubmit = jest.fn().mockImplementation(() => error);
584+
const managerApi = createManagerApi({ onSubmit });
585+
const { registerField } = managerApi();
586+
587+
const render = jest.fn();
588+
registerField({ name: 'field', render });
589+
590+
expect(render).not.toHaveBeenCalled();
591+
592+
managerApi().handleSubmit();
593+
594+
expect(render).toHaveBeenCalled();
595+
expect(managerApi().submitting).toEqual(false);
596+
expect(managerApi().submitError).toEqual(undefined);
597+
expect(managerApi().submitFailed).toEqual(true);
598+
expect(managerApi().submitSucceeded).toEqual(false);
599+
expect(managerApi().submitErrors).toEqual(error);
600+
expect(managerApi().hasSubmitErrors).toEqual(true);
601+
});
602+
603+
it('onsubmit receives an error - form level', () => {
604+
const error = { [FORM_ERROR]: 'some-error' };
605+
const onSubmit = jest.fn().mockImplementation(() => error);
606+
const managerApi = createManagerApi({ onSubmit });
607+
const { registerField } = managerApi();
608+
609+
const render = jest.fn();
610+
registerField({ name: 'field', render });
611+
612+
expect(render).not.toHaveBeenCalled();
613+
614+
managerApi().handleSubmit();
615+
616+
expect(render).toHaveBeenCalled();
617+
expect(managerApi().submitting).toEqual(false);
618+
expect(managerApi().submitError).toEqual('some-error');
619+
expect(managerApi().submitFailed).toEqual(true);
620+
expect(managerApi().submitSucceeded).toEqual(false);
621+
expect(managerApi().submitErrors).toEqual(error);
622+
expect(managerApi().hasSubmitErrors).toEqual(true);
573623
});
574624

575625
it('getField state should return correct field state', () => {
@@ -1726,4 +1776,104 @@ describe('managerApi', () => {
17261776
expect(onSubmit).not.toHaveBeenCalled();
17271777
});
17281778
});
1779+
1780+
describe('async submit', () => {
1781+
it('calls async submit', async () => {
1782+
jest.useFakeTimers();
1783+
1784+
const onSubmit = jest.fn().mockImplementation(() => new Promise((res) => setTimeout(() => res('ok'), 1000)));
1785+
const render = jest.fn();
1786+
1787+
const managerApi = createManagerApi({ onSubmit });
1788+
1789+
managerApi().registerField({ name: 'field', internalId: '1', render });
1790+
1791+
expect(managerApi().submitting).toEqual(false);
1792+
expect(render).not.toHaveBeenCalled();
1793+
1794+
managerApi().submit();
1795+
1796+
expect(render).toHaveBeenCalled();
1797+
render.mockClear();
1798+
expect(managerApi().submitting).toEqual(true);
1799+
1800+
await jest.runAllTimers();
1801+
1802+
expect(render).toHaveBeenCalled();
1803+
expect(managerApi().submitting).toEqual(false);
1804+
expect(managerApi().submitError).toEqual(undefined);
1805+
expect(managerApi().submitFailed).toEqual(false);
1806+
expect(managerApi().submitSucceeded).toEqual(true);
1807+
expect(managerApi().submitErrors).toEqual(undefined);
1808+
expect(managerApi().hasSubmitErrors).toEqual(false);
1809+
});
1810+
1811+
it('calls async submit - failed', async () => {
1812+
jest.useFakeTimers();
1813+
1814+
const error = 'some evil error';
1815+
1816+
const onSubmit = jest.fn().mockImplementation(() => new Promise((res, rej) => setTimeout(() => rej(error), 100)));
1817+
const render = jest.fn();
1818+
1819+
const managerApi = createManagerApi({ onSubmit });
1820+
1821+
managerApi().registerField({ name: 'field', internalId: '1', render });
1822+
1823+
expect(managerApi().submitting).toEqual(false);
1824+
expect(render).not.toHaveBeenCalled();
1825+
1826+
managerApi().submit();
1827+
1828+
expect(render).toHaveBeenCalled();
1829+
render.mockClear();
1830+
1831+
expect(managerApi().submitting).toEqual(true);
1832+
1833+
await jest.runAllTimers();
1834+
await jest.runAllTimers(); // for some reason, catch branch is not triggerd on first run
1835+
1836+
expect(render).toHaveBeenCalled();
1837+
expect(managerApi().submitError).toEqual(undefined);
1838+
expect(managerApi().submitting).toEqual(false);
1839+
expect(managerApi().submitFailed).toEqual(true);
1840+
expect(managerApi().submitSucceeded).toEqual(false);
1841+
expect(managerApi().submitErrors).toEqual(error);
1842+
expect(managerApi().hasSubmitErrors).toEqual(true);
1843+
});
1844+
1845+
it('calls async submit - failed with form level', async () => {
1846+
jest.useFakeTimers();
1847+
1848+
const error = { [FORM_ERROR]: 'some evil error' };
1849+
1850+
const onSubmit = jest.fn().mockImplementation(() => new Promise((res, rej) => setTimeout(() => rej(error), 100)));
1851+
const render = jest.fn();
1852+
1853+
const managerApi = createManagerApi({ onSubmit });
1854+
1855+
managerApi().registerField({ name: 'field', internalId: '1', render });
1856+
1857+
expect(managerApi().submitting).toEqual(false);
1858+
expect(render).not.toHaveBeenCalled();
1859+
1860+
managerApi().submit();
1861+
1862+
expect(render).toHaveBeenCalled();
1863+
render.mockClear();
1864+
1865+
expect(managerApi().submitting).toEqual(true);
1866+
1867+
await jest.runAllTimers();
1868+
await jest.runAllTimers(); // for some reason, catch branch is not triggerd on first run
1869+
1870+
expect(render).toHaveBeenCalled();
1871+
expect(managerApi().submitError).toEqual('some evil error');
1872+
expect(managerApi().submitting).toEqual(false);
1873+
expect(managerApi().submitFailed).toEqual(true);
1874+
expect(managerApi().submitSucceeded).toEqual(false);
1875+
expect(managerApi().submitErrors).toEqual(error);
1876+
expect(managerApi().hasSubmitErrors).toEqual(true);
1877+
});
1878+
});
17291879
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export type HandleSubmit = (event?: FormEvent) => void;
2424
export type RegisterField = (field: FieldConfig) => void;
2525
export type UnregisterField = (field: Omit<FieldConfig, 'render'>) => void;
2626
export type GetState = () => ManagerState;
27-
export type OnSubmit = (values: AnyObject) => void;
27+
export type OnSubmit = (values: AnyObject) => any;
2828
export type GetFieldValue = (name: string) => any;
2929
export type GetFieldState = (name: string) => ExtendedFieldState | undefined;
3030
export type Focus = (name: string) => void;

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

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { Meta } from '../types/use-field';
2727
import { formLevelValidator, isPromise } from './validate';
2828
import { FormValidator, FormLevelError, Validator } from '../types/validate';
2929
import findDifference from './find-difference';
30+
import FORM_ERROR from '../files/form-error';
3031

3132
export const defaultIsEqual = (a: any, b: any) => a === b;
3233

@@ -540,8 +541,59 @@ const createManagerApi: CreateManagerApi = ({
540541
return;
541542
}
542543

543-
config.onSubmit(state.values);
544+
const result = config.onSubmit(state.values);
544545

546+
if (isPromise(result)) {
547+
updateSubmitting(true);
548+
const render = prepareRerender();
549+
550+
result
551+
.then(() => {
552+
state.submitErrors = undefined;
553+
state.hasSubmitErrors = false;
554+
state.submitFailed = false;
555+
state.submitSucceeded = true;
556+
state.submitting = false;
557+
state.submitError = undefined;
558+
559+
render();
560+
561+
runAfterSubmit();
562+
})
563+
.catch((error: unknown) => {
564+
state.submitErrors = error as AnyObject;
565+
state.hasSubmitErrors = true;
566+
state.submitFailed = true;
567+
state.submitSucceeded = false;
568+
state.submitting = false;
569+
state.submitError = state.submitErrors?.[FORM_ERROR];
570+
571+
render();
572+
});
573+
} else {
574+
const render = prepareRerender();
575+
576+
if (result) {
577+
state.submitErrors = result;
578+
state.hasSubmitErrors = true;
579+
state.submitFailed = true;
580+
state.submitSucceeded = false;
581+
state.submitError = state.submitErrors?.[FORM_ERROR];
582+
} else {
583+
state.submitErrors = undefined;
584+
state.hasSubmitErrors = false;
585+
state.submitFailed = false;
586+
state.submitSucceeded = true;
587+
state.submitError = undefined;
588+
}
589+
590+
render();
591+
592+
runAfterSubmit();
593+
}
594+
}
595+
596+
function runAfterSubmit() {
545597
state.registeredFields.forEach((name) =>
546598
traverseObject(state.fieldListeners[name].fields, (field) => {
547599
field.afterSubmit && field.afterSubmit();

0 commit comments

Comments
 (0)