Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add submitting state property that is true while onSubmit is pending #449

Merged
merged 44 commits into from
Nov 25, 2018
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1533cfb
Reformat initial state in tests in preparation for expansion
BudgieInWA Jul 17, 2018
407efef
Add additional checks to existing tests to increase coverage
BudgieInWA Jul 17, 2018
099ca9a
Add straightforward tracking of `submitting` state when submitting
BudgieInWA Jul 17, 2018
1b4e727
Skip setting `submitting` state when `onSubmit` is not async.
BudgieInWA Jul 17, 2018
53a9690
Catch errors (synchronously) thrown by `onSubmit`
BudgieInWA Jul 17, 2018
b7df3e0
Add `submitting` state to docs
BudgieInWA Jul 17, 2018
bed378d
Add test case for `onSubmit` throwing
BudgieInWA Jul 17, 2018
b65eff0
Fix ordering of new `filterDOMProps` `unwantedProp`
BudgieInWA Jul 18, 2018
cb10d5e
Initialise `submitting` to false from the beginning instead of follow…
BudgieInWA Jul 18, 2018
70cc1e8
Use promise `finally` instead of `then`
BudgieInWA Jul 18, 2018
89668b8
Rename `res` to `result`
BudgieInWA Jul 18, 2018
a81fbb2
Revert "Catch errors (synchronously) thrown by `onSubmit`"
BudgieInWA Jul 19, 2018
be04d79
Revert "Add test case for `onSubmit` throwing"
BudgieInWA Jul 19, 2018
5bfc060
Include reason in comment about conditionally setting `submitting` state
BudgieInWA Jul 19, 2018
6b839a9
Re-add check for `submitting` in the default context test
BudgieInWA Jul 20, 2018
4eee44a
Rewrite tests to do with changing props
BudgieInWA Jul 20, 2018
edefb1d
Re-organise and -write tests, grouping them by event
BudgieInWA Jul 25, 2018
2d38fc5
Add tests around interactions with schema validator
BudgieInWA Jul 25, 2018
43b0fa1
Reformat and reorder
BudgieInWA Sep 8, 2018
86690f2
Expand prop changing tests
BudgieInWA Sep 8, 2018
fa4dc6b
Fix test "lets `onValidate` suppress `validator` errors" (I think)
BudgieInWA Sep 8, 2018
b789830
Add missing line
BudgieInWA Sep 8, 2018
106a18d
Rewrite ValidatedForm test suite
BudgieInWA Sep 8, 2018
b4dfccb
Fix code style errors
BudgieInWA Sep 10, 2018
a9d7566
Refactor broken test in terms of validation instead of submission
BudgieInWA Sep 10, 2018
18fc7ac
There is no need to pass `onSubmit` after all
BudgieInWA Sep 10, 2018
ac87ecf
Merge branch 'validated-form-tests-1' into validated-form-tests
BudgieInWA Sep 10, 2018
e092d04
Add option for async onValidate and onSubmit to demo
BudgieInWA Sep 11, 2018
3515b43
Add tests for `validating` context attribute
BudgieInWA Sep 11, 2018
033c963
Include `validating` state in ValidatedForm childContext
BudgieInWA Sep 11, 2018
e680480
Merge remote-tracking branch 'upstream/master' into submitting-state-1
BudgieInWA Sep 11, 2018
7c51b6e
Merge branch 'validated-form-tests' into submitting-state-1
BudgieInWA Sep 11, 2018
9e9aa69
Revert .gitignore changes
BudgieInWA Sep 11, 2018
551f053
Add validating state to the example `SubmitField` impl
BudgieInWA Sep 11, 2018
0d529d1
Add `validating` state to `__reset`
BudgieInWA Sep 11, 2018
50ff7d5
Add TODO about `childContextTypes`
BudgieInWA Sep 11, 2018
639ea96
Add `validating` to filterDOMProps.js
BudgieInWA Sep 11, 2018
83d6ba2
Save on a `Promise` construction in the async case
BudgieInWA Sep 11, 2018
ae584c9
Remove mistaken line in `getNativeFormProps`
BudgieInWA Sep 11, 2018
29f8ed8
Make onValidate#onSubmit set `submitting` true (then false)
BudgieInWA Nov 13, 2018
d2dd32a
Change `.catch().finally()` to `.catch().then()`
BudgieInWA Nov 15, 2018
3640cde
Add `static plainChildContextTypes` to Forms, for use when extending
BudgieInWA Nov 19, 2018
0e66889
Remove unused name and fix indentation
BudgieInWA Nov 19, 2018
056f084
Reorganized context types.
radekmie Nov 25, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions INTRODUCTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ MyComponentUsingUniformsContext.contextTypes = {
state: PropTypes.shape({
changed: PropTypes.bool.isRequired,
changedMap: PropTypes.object.isRequired,
submitting: PropTypes.bool.isRequired,

label: PropTypes.bool.isRequired,
disabled: PropTypes.bool.isRequired,
Expand Down Expand Up @@ -948,8 +949,8 @@ import filterDOMProps from 'uniforms/filterDOMProps';
// This field works as follows: render standard submit field and disable it, when
// the form is invalid. It's a simplified version of a default SubmitField from
// uniforms-unstyled.
const SubmitField = (props, {uniforms: {error, state: {disabled}}}) =>
<input disabled={!!(error || disabled)} type="submit" />
const SubmitField = (props, {uniforms: {error, state: {disabled, submitting}}}) =>
<input disabled={!!(error || disabled || submitting)} type="submit" />
;

SubmitField.contextTypes = BaseField.contextTypes;
Expand Down
1 change: 1 addition & 0 deletions packages/uniforms-antd/__tests__/_createContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const createContext = (schema, context) => ({
changedMap: {},

changed: false,
submitting: false,
disabled: false,
label: false,
placeholder: false,
Expand Down
1 change: 1 addition & 0 deletions packages/uniforms-bootstrap3/__tests__/_createContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const createContext = (schema, context) => ({
changedMap: {},

changed: false,
submitting: false,
disabled: false,
label: false,
placeholder: false,
Expand Down
1 change: 1 addition & 0 deletions packages/uniforms-bootstrap4/__tests__/_createContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const createContext = (schema, context) => ({
changedMap: {},

changed: false,
submitting: false,
disabled: false,
label: false,
placeholder: false,
Expand Down
1 change: 1 addition & 0 deletions packages/uniforms-material/__tests__/_createContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const createContext = (schema, context) => ({

changed: false,
disabled: false,
submitting: false,
label: false,
placeholder: false,
showInlineError: false,
Expand Down
1 change: 1 addition & 0 deletions packages/uniforms-semantic/__tests__/_createContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const createContext = (schema, context) => ({
changedMap: {},

changed: false,
submitting: false,
disabled: false,
label: false,
placeholder: false,
Expand Down
1 change: 1 addition & 0 deletions packages/uniforms-unstyled/__tests__/_createContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const createContext = (schema, context) => ({
changedMap: {},

changed: false,
submitting: false,
disabled: false,
label: false,
placeholder: false,
Expand Down
10 changes: 9 additions & 1 deletion packages/uniforms/__tests__/BaseField.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,15 @@ describe('BaseField', () => {
const model = {a: {b: {c: 'example'}}};
const onChange = jest.fn();
const randomId = randomIds();
const state = {changed: !1, changedMap: {}, label: !0, disabled: !1, placeholder: !0, showInlineError: !0};
const state = {
changed: false,
changedMap: {},
submitting: false,
label: true,
disabled: false,
placeholder: true,
showInlineError: true
};
const schema = createSchemaBridge({
getDefinition (name) {
// Simulate SimpleSchema.
Expand Down
43 changes: 36 additions & 7 deletions packages/uniforms/__tests__/BaseForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ describe('BaseForm', () => {
afterEach(() => {
onChange.mockReset();
onSubmit.mockReset();
onSubmitSuccess.mockReset();
onSubmitFailure.mockReset();
});

describe('child context', () => {
Expand Down Expand Up @@ -254,7 +256,7 @@ describe('BaseForm', () => {
expect(onSubmit).toHaveBeenLastCalledWith(model);
});

it('calls `onSubmit` with correct model (`modelTransform`)', () => {
it('calls `onSubmit` with the correctly `modelTransform`ed model', () => {
wrapper.setProps({
modelTransform (mode, model) {
if (mode === 'submit') {
Expand All @@ -272,15 +274,39 @@ describe('BaseForm', () => {
wrapper.setProps({modelTransform: undefined});
});

it('does nothing without `onSubmit`', () => {
wrapper.setProps({onSubmit: undefined});
it('without `onSubmit` calls only `onSubmitSuccess`', async () => {
wrapper.setProps({onSubmit: undefined, onSubmitSuccess, onSubmitFailure});
wrapper.find('form').simulate('submit');

await new Promise(resolve => process.nextTick(resolve));
expect(onSubmit).not.toBeCalled();
expect(onSubmitSuccess).toBeCalledTimes(1);
expect(onSubmitFailure).not.toBeCalled();
});

it('sets `submitting` state', async () => {
let resolveSubmit = null;
wrapper.setProps({onSubmit: () => new Promise(resolve => resolveSubmit = resolve)});

const context1 = wrapper.instance().getChildContext().uniforms.state;
expect(context1).toHaveProperty('submitting', false);

wrapper.find('form').simulate('submit');
await new Promise(resolve => process.nextTick(resolve));

const context2 = wrapper.instance().getChildContext().uniforms.state;
expect(context2).toHaveProperty('submitting', true);

resolveSubmit();
await new Promise(resolve => process.nextTick(resolve));

const context3 = wrapper.instance().getChildContext().uniforms.state;
expect(context3).toHaveProperty('submitting', false);
});

it('calls `onSubmitSuccess` when `onSubmit` resolves', async () => {
onSubmit.mockReturnValueOnce(Promise.resolve());
it('calls `onSubmitSuccess` with the returned value when `onSubmit` resolves', async () => {
const onSubmitValue = 'value';
onSubmit.mockReturnValueOnce(Promise.resolve(onSubmitValue));

const wrapper = mount(
<BaseForm model={model} schema={schema} onSubmit={onSubmit} onSubmitSuccess={onSubmitSuccess} />
Expand All @@ -291,10 +317,12 @@ describe('BaseForm', () => {
await new Promise(resolve => process.nextTick(resolve));

expect(onSubmitSuccess).toHaveBeenCalledTimes(1);
expect(onSubmitSuccess).toHaveBeenLastCalledWith(onSubmitValue);
});

it('calls `onSubmitFailure` when `onSubmit` rejects', async () => {
onSubmit.mockReturnValueOnce(Promise.reject());
it('calls `onSubmitFailure` with the thrown error when `onSubmit` rejects', async () => {
const onSubmitError = 'error';
onSubmit.mockReturnValueOnce(Promise.reject(onSubmitError));

const wrapper = mount(
<BaseForm model={model} schema={schema} onSubmit={onSubmit} onSubmitFailure={onSubmitFailure} />
Expand All @@ -305,6 +333,7 @@ describe('BaseForm', () => {
await new Promise(resolve => process.nextTick(resolve));

expect(onSubmitFailure).toHaveBeenCalledTimes(1);
expect(onSubmitFailure).toHaveBeenLastCalledWith(onSubmitError);
});
});
});
10 changes: 9 additions & 1 deletion packages/uniforms/__tests__/connectField.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ describe('connectField', () => {
const error = new Error();
const onChange = jest.fn();
const randomId = randomIds();
const state = {changed: !1, changedMap: {}, label: !0, disabled: !1, placeholder: !1, showInlineError: !0};
const state = {
changed: false,
changedMap: {},
submitting: false,
label: true,
disabled: false,
placeholder: false,
showInlineError: true
};
const schema = createSchemaBridge({
getDefinition (name) {
return {
Expand Down
10 changes: 9 additions & 1 deletion packages/uniforms/__tests__/injectName.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@ describe('injectName', () => {
const error = new Error();
const onChange = () => {};
const randomId = randomIds();
const state = {changed: !1, changedMap: {}, label: !0, disabled: !1, placeholder: !1, showInlineError: !0};
const state = {
changed: false,
changedMap: {},
submitting: false,
label: true,
disabled: false,
placeholder: false,
showInlineError: true
};
const schema = createSchemaBridge({
getDefinition (name) {
return {
Expand Down
31 changes: 23 additions & 8 deletions packages/uniforms/src/BaseForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import cloneDeep from 'lodash/cloneDeep';
import get from 'lodash/get';
import set from 'lodash/set';
import isFunction from 'lodash/isFunction';
import {Component} from 'react';

import changedKeys from './changedKeys';
Expand Down Expand Up @@ -65,6 +66,7 @@ export default class BaseForm extends Component {
state: PropTypes.shape({
changed: PropTypes.bool.isRequired,
changedMap: PropTypes.object.isRequired,
submitting: PropTypes.bool.isRequired,

label: PropTypes.bool.isRequired,
disabled: PropTypes.bool.isRequired,
Expand All @@ -80,7 +82,13 @@ export default class BaseForm extends Component {
constructor () {
super(...arguments);

this.state = {bridge: createSchemaBridge(this.props.schema), changed: null, changedMap: {}, resetCount: 0};
this.state = {
bridge: createSchemaBridge(this.props.schema),
changed: null,
changedMap: {},
submitting: false,
resetCount: 0
};

this.delayId = false;
this.randomId = randomIds(this.props.id);
Expand Down Expand Up @@ -126,8 +134,9 @@ export default class BaseForm extends Component {

getChildContextState () {
return {
changed: !!this.state.changed,
changedMap: this.state.changedMap,
changed: !!this.state.changed,
changedMap: this.state.changedMap,
submitting: !!this.state.submitting,

label: !!this.props.label,
disabled: !!this.props.disabled,
Expand Down Expand Up @@ -225,7 +234,7 @@ export default class BaseForm extends Component {
}

__reset (state) {
return {changed: false, changedMap: {}, resetCount: state.resetCount + 1};
return {changed: false, changedMap: {}, submitting: false, resetCount: state.resetCount + 1};
}

onReset () {
Expand All @@ -238,10 +247,16 @@ export default class BaseForm extends Component {
event.stopPropagation();
}

return Promise.resolve(
this.props.onSubmit &&
this.props.onSubmit(this.getModel('submit'))
).then(
const result = this.props.onSubmit && this.props.onSubmit(this.getModel('submit'));
let submitting = Promise.resolve(result);

// Do not change the `submitting` state if onSubmit is not async
if (result && isFunction(result.then)) {
this.setState({submitting: true});
submitting = submitting.finally(() => this.setState({submitting: false}));
}
radekmie marked this conversation as resolved.
Show resolved Hide resolved

return submitting.then(
radekmie marked this conversation as resolved.
Show resolved Hide resolved
this.props.onSubmitSuccess,
this.props.onSubmitFailure
);
Expand Down
1 change: 1 addition & 0 deletions packages/uniforms/src/filterDOMProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const unwantedProps = [
'parent',
'placeholder',
'showInlineError',
'submitting',
'transform',
'value',

Expand Down