How to make server-side validation using redux-form and Fetch API? There are "Submit Validation" demo provided in the docs which says that recommended way to do server side validation is to return a promise from the onSubmit function. But where should I place that promise? As I understood onSubmit function should be my action.
<form onSubmit={this.props.addWidget}>...
Where this.props.addWidget is actually my action, provided below.
import fetch from 'isomorphic-fetch';
...
function fetchAddWidget(widget, workspace) {
return dispatch => {
dispatch(requestAddWidget(widget, workspace));
return fetch.post(`/service/workspace/${workspace}/widget`, widget)
.then(parseJSON)
.then(json => {
dispatch(successAddWidget(json, workspace));
DataManager.handleSubscribes(json);
})
.catch(error => popupErrorMessages(error));
}
}
export function addWidget(data, workspace) {
return (dispatch, getState) => {
return dispatch(fetchAddWidget(data, workspace));
}
}
As you see I use fetch API. I expected that fetch will return promise and redux-form will catch it but that doesn't work. How to make it work with promise from example?
Also from the demo I can not understand what should be provided in this.props.handleSubmit function. Demo does not explain this part, as for me.
Here's my take on using fetch based on the example at http://erikras.github.io/redux-form/#/examples/submit-validation.
- ...but where should I place that promise?
- ...what should be provided in this.props.handleSubmit?
The detail is in the comments below; sorry that the code blocks require a bit of scrolling to read :/
components/submitValidation.js
import React, { Component, PropTypes } from 'react';
import { reduxForm } from 'redux-form';
import { myHandleSubmit, show as showResults } from '../redux/modules/submission';
class SubmitValidationForm extends Component {
// the following three props are all provided by the reduxForm() wrapper / decorator
static propTypes = {
// the field names we passed in the wrapper;
// each field is now an object with properties:
// value, error, touched, dirty, etc
// and methods onFocus, onBlur, etc
fields: PropTypes.object.isRequired,
// handleSubmit is _how_ to handle submission:
// eg, preventDefault, validate, etc
// not _what_ constitutes or follows success or fail.. that's up to us
// I must pass a submit function to this form, but I can either:
// a) import or define a function in this component (see above), then:
// `<form onSubmit={ this.props.handleSubmit(myHandleSubmit) }>`, or
// b) pass that function to this component as
// `<SubmitValidationForm onSubmit={ myHandleSubmit } etc />`, then
// `<form onSubmit={this.props.handleSubmit}>`
handleSubmit: PropTypes.func.isRequired,
// redux-form listens for `reject({_error: 'my error'})`, we receive `this.props.error`
error: PropTypes.string
};
render() {
const { fields: { username, password }, error, handleSubmit } = this.props;
return (
<form onSubmit={ handleSubmit(myHandleSubmit) }>
<input type="text" {...username} />
{
// this can be read as "if touched and error, then render div"
username.touched && username.error && <div className="form-error">{ username.error }</div>
}
<input type="password" {...password} />
{ password.touched && password.error && <div className="form-error">{ password.error }</div> }
{
// this is the generic error, passed through as { _error: 'something wrong' }
error && <div className="text-center text-danger">{ error }</div>
}
// not sure why in the example @erikras uses
// `onClick={ handleSubmit }` here.. I suspect a typo.
// because I'm using `type="submit"` this button will trigger onSubmit
<button type="submit">Log In</button>
</form>
);
}
}
// this is the Higher Order Component I've been referring to
// as the wrapper, and it may also be written as a @decorator
export default reduxForm({
form: 'submitValidation',
fields: ['username', 'password'] // we send only field names here
})(SubmitValidationForm);
../redux/modules/submission.js
// (assume appropriate imports)
function postToApi(values) {
return fetch( API_ENDPOINT, {
credentials: 'include',
mode: 'cors',
method: 'post',
body: JSON.stringify({values}),
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': CSRF_TOKEN
}
}).then( response => Promise.all([ response, response.json()] ));
}
export const myHandleSubmit = (values, dispatch) => {
dispatch(startLoading());
return new Promise((resolve, reject) => {
// postToApi is a wrapper around fetch
postToApi(values)
.then(([ response, json ]) => {
dispatch(stopLoading());
// your statuses may be different, I only care about 202 and 400
if (response.status === 202) {
dispatch(showResults(values));
resolve();
}
else if (response.status === 400) {
// here I expect that the server will return the shape:
// {
// username: 'User does not exist',
// password: 'Wrong password',
// _error: 'Login failed!'
// }
reject(json.errors);
}
else {
// we're not sure what happened, but handle it:
// our Error will get passed straight to `.catch()`
throw(new Error('Something went horribly wrong!'));
}
})
.catch( error => {
// Otherwise unhandled server error
dispatch(stopLoading());
reject({ _error: error });
});
});
};
Please chime in with comments if I've missed something / misinterpreted, etc, and I'll amend :)
It turned out that there are undocumented property returnRejectedSubmitPromise
which must be set to true.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With