Redux Form provides way to make asynchronous submit validation, but API can use only promises. For Redux applications, using actions is more natural. Especially when you are using asynchronous flow abstraction like sagas or observables.
Redux Form Actions is wrapper around Redux Form with action driven submit validations.
npm install --save redux-form-actions
Declare action types and actions for form handling
export const SUBMIT_CONTACT_FORM = 'SUBMIT_CONTACT_FORM';
export const SUBMIT_CONTACT_OK = 'SUBMIT_CONTACT_OK';
export const SUBMIT_CONTACT_FAILED = 'SUBMIT_CONTACT_FAILED';
export const submitContactForm = (values) => ({
type: SUBMIT_CONTACT_FORM,
payload: value
});
export const submitContactOk = () => ({
type: SUBMIT_CONTACT_OK
});
export const submitContactFailed = (err) => ({
type: SUBMIT_CONTACT_FAILED,
payload: err
});
Create form component same as with Redux Form,
see https://redux-form.com/6.1.1/docs/GettingStarted.md/#step-2
For convenience redux-form-actions
re-export all stuff from redux-form
so
you can import fields also from redux-form-actions
module.
Decorate the form component using reduxForm from redux-form-actions
.
import { Field, reduxForm } from 'redux-form-actions';
// ContactForm = ....
export default reduxForm({
form: 'contact' // a unique name for this form
})(ContactForm);
Put form to your container. Same as with plain Redux Form onSubmit
is
called when user submits form, but instead of returning promise we just
dispatch action. In this example action creator is wrapped with dispatch by
standard react-redux connect.
Form submit is resolved when action matching
succeededAction
or failedAction
type is dispatched.
import * as Actions, { submitContactForm } from '../actions';
class ContactPage extends React.Component {
render() {
return (
<ContactForm
onSubmit={this.props.submitContactForm}
succeededAction={Actions.SUBMIT_CONTACT_OK}
failedAction={Actions.SUBMIT_CONTACT_FAILED}
/>
);
}
}
export default connect(
state => ({}), { submitContactForm }
)(ContactPage);
Middleware is essential to handle declared succeededAction
and failedActions
.
Use middleware in addition to original form reducer.
import { reduxFormMiddleware, reducer as formReducer } from 'redux-form-actions';
const reducers = {
// ... your other reducers here ...
form: formReducer // <---- Mounted at 'form'
}
const store = createStore(
rootReducer,
applyMiddleware(
reduxFormMiddleware
)
);
Finally put all together. Use you favorite approach - thunk, saga or observable.
redux-observable epic:
export default action$ => {
return action$
.ofType(Actions.SUBMIT_CONTACT_FORM)
.mergeMap(action => {
const { form, values } = action.payload;
return ajax.post(`/submits/${form}`, values)
.map(submitContactOk)
.catch(submitContactFailed);
});
};
Alternatively, same logic, using redux-saga saga:
function* constactFormSaga() {
while (true) {
const action = yield take(Actions.SUBMIT_CONTACT_FORM);
const { form, values } = action.payload;
try {
yield call(FormApi.submit, `/submits/${form}`, values);
yield put(submitContactOk());
} catch (err) {
yield put(submitContactFailed(err));
}
}
API is same as redux-form except following reduxForm()
changes:
onSubmit
:
If succeededAction
is also declared onSubmit is considered asynchronous and resolved with future action.
Otherwise behave same as plain reduxForm callback.
succeededAction
: string | (action) => boolean
Action type or predicate. Matching action resolves submit as succeeded.
failedAction
: string | (action) => boolean
Action type or predicate. Matching action resolves submit as failed.
Like plain Redux Form, you can use options as reduxForm()
argument or declare
them as component props.
redux-form-actions
provide Immutable.JS variant in same way as original redux-form
.
import { reduxForm } from 'redux-form-actions/immutable';