Skip to content
This repository was archived by the owner on Aug 23, 2022. It is now read-only.

Commit e14782e

Browse files
committed
Adding onSubmitFailed + tests + documentation. Fixes #539
1 parent ebe60d7 commit e14782e

File tree

4 files changed

+121
-5
lines changed

4 files changed

+121
-5
lines changed

docs/api/Form.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,50 @@ export default connect(null)(MyForm);
140140

141141
### Notes
142142
- You can do anything in `onSubmit`; including firing off custom actions or handling (async) validation yourself.
143+
- `onSubmit` can also be triggered remotely by dispatching `actions.submit(model)` where `model` is the model of the form and no other arguments are provided. It will be called if the form is valid and able to be submitted.
144+
145+
## `onSubmitFailed={...}`
146+
_(Function)_: The handler function called when the form fails to submit. This happens when:
147+
- attempting to submit an invalid form
148+
- submitting a valid form that later becomes invalid (due to async server/API validation, etc.)
149+
150+
The callback function provided to `onSubmitFailed` will be called with one argument: the entire `formState` for the form's `model`.
151+
152+
### Example
153+
```jsx
154+
import React from 'react';
155+
import { connect } from 'react-redux';
156+
import { Form, Control, actions } from 'react-redux-form';
157+
158+
class MyForm extends React.Component {
159+
handleSubmitFailed(userForm) {
160+
// logs form-level errors
161+
console.log(userForm.$form.errors);
162+
163+
// logs errors for user.email
164+
console.log(userForm.email.errors);
165+
}
166+
167+
render() {
168+
return (
169+
<Form
170+
model="user"
171+
validators={{...}}
172+
onSubmit={...}
173+
onSubmitFailed={ (userForm) => this.handleSubmitFailed(userForm) }
174+
>
175+
<Control type="email" model=".email" />
176+
</Form>
177+
);
178+
}
179+
}
180+
181+
export default connect(null)(MyForm);
182+
```
183+
184+
### Notes
185+
- This can also be used (and is extremely useful) with `<LocalForm>`.
186+
- Remotely triggering a submit on the form by dispatching `actions.submit(model)` will also call this callback if the form submission fails.
143187

144188
## `component={...}`
145189
_(Any)_ The `component` that the `<Form>` should be rendered to (default: `"form"`).

src/components/form-component.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const propTypes = {
2929
modelValue: PropTypes.any,
3030
formValue: PropTypes.object,
3131
onSubmit: PropTypes.func,
32+
onSubmitFailed: PropTypes.func,
3233
dispatch: PropTypes.func,
3334
children: PropTypes.oneOfType([
3435
PropTypes.func,
@@ -221,7 +222,15 @@ function createFormClass(s = defaultStrategy) {
221222
}
222223

223224
handleInvalidSubmit() {
224-
this.props.dispatch(s.actions.setSubmitFailed(this.props.model));
225+
const { onSubmitFailed, formValue, dispatch } = this.props;
226+
227+
if (onSubmitFailed) {
228+
onSubmitFailed(formValue);
229+
}
230+
231+
if (!formValue.$form.submitFailed) {
232+
dispatch(s.actions.setSubmitFailed(this.props.model));
233+
}
225234
}
226235

227236
handleReset(e) {

src/reducers/form-actions-reducer.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,24 @@ const setInitialFieldState = (field, key) => {
6060
});
6161
};
6262

63+
const addIntent = (intents, newIntent) => {
64+
if (!intents) return [newIntent];
65+
if (intents.some(intent => intent.type === newIntent.type)) return intents;
66+
67+
return intents.concat(newIntent);
68+
};
69+
70+
const clearIntents = (intents, oldIntent) => {
71+
if (!intents || typeof oldIntent === 'undefined') return [];
72+
return intents.filter(intent => intent.type !== oldIntent.type);
73+
};
74+
6375
export default function formActionsReducer(state, action, localPath) {
6476
const [field] = getFieldAndForm(state, localPath);
6577
const fieldState = field && field.$form
6678
? field.$form
6779
: field;
80+
const { intents } = fieldState;
6881

6982
let fieldUpdates = {};
7083
let subFieldUpdates = {};
@@ -75,8 +88,8 @@ export default function formActionsReducer(state, action, localPath) {
7588
fieldUpdates = {
7689
focus: true,
7790
intents: action.silent
78-
? []
79-
: [action],
91+
? intents
92+
: addIntent(intents, action),
8093
};
8194

8295
break;
@@ -261,15 +274,15 @@ export default function formActionsReducer(state, action, localPath) {
261274

262275
case actionTypes.ADD_INTENT: {
263276
fieldUpdates = {
264-
intents: [action.intent],
277+
intents: addIntent(intents, action.intent),
265278
};
266279

267280
break;
268281
}
269282

270283
case actionTypes.CLEAR_INTENTS: {
271284
fieldUpdates = {
272-
intents: [],
285+
intents: clearIntents(intents, action.intent),
273286
};
274287

275288
break;

test/form-component-spec.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,5 +1837,55 @@ Object.keys(testContexts).forEach((testKey) => {
18371837
assert.isTrue(handleSubmit.calledOnce);
18381838
});
18391839
});
1840+
1841+
describe('onSubmitFailed() prop', () => {
1842+
it('should call onSubmitFailed() prop if submit attempted with invalid form', () => {
1843+
const initialState = getInitialState({ foo: '' });
1844+
1845+
const store = testCreateStore({
1846+
test: modelReducer('test', initialState),
1847+
testForm: formReducer('test', initialState),
1848+
});
1849+
1850+
let handleSubmitFailedCalledWith = null;
1851+
1852+
function handleSubmitFailed(val) {
1853+
handleSubmitFailedCalledWith = val;
1854+
}
1855+
1856+
const form = TestUtils.renderIntoDocument(
1857+
<Provider store={store}>
1858+
<Form
1859+
model="test"
1860+
onSubmitFailed={handleSubmitFailed}
1861+
validators={{
1862+
foo: (val) => val.length,
1863+
}}
1864+
>
1865+
<Control model=".foo" />
1866+
</Form>
1867+
</Provider>
1868+
);
1869+
1870+
const formNode = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
1871+
1872+
TestUtils.Simulate.submit(formNode);
1873+
1874+
assert.containSubset(handleSubmitFailedCalledWith, {
1875+
$form: {
1876+
model: 'test',
1877+
valid: false,
1878+
},
1879+
foo: {
1880+
model: 'test.foo',
1881+
valid: false,
1882+
errors: true,
1883+
},
1884+
});
1885+
1886+
assert.isFalse(store.getState().testForm.$form.pending);
1887+
assert.isTrue(store.getState().testForm.$form.submitFailed);
1888+
});
1889+
});
18401890
});
18411891
});

0 commit comments

Comments
 (0)