How Can I Show Customized Error Messaged From Server Side Validation In React Admin Package?
Solution 1:
If you're using SimpleForm, you can use asyncValidate
together with asyncBlurFields
as suggested in a comment in issue 97. I didn't use SimpleForm, so this is all I can tell you about that.
I've used a simple form
. And you can use server-side validation there as well. Here's how I've done it. A complete and working example.
importReactfrom'react';
importPropTypesfrom'prop-types';
import { Field, propTypes, reduxForm, SubmissionError } from'redux-form';
import { connect } from'react-redux';
import compose from'recompose/compose';
import { CardActions } from'material-ui/Card';
importButtonfrom'material-ui/Button';
importTextFieldfrom'material-ui/TextField';
import { CircularProgress } from'material-ui/Progress';
import { CREATE, translate } from'ra-core';
import { dataProvider } from'../../providers'; // <-- Make sure to import yours!constrenderInput = ({
meta: { touched, error } = {},
input: { ...inputProps },
...props
}) => (
<TextFielderror={!!(touched && error)}
helperText={touched && error}
{...inputProps}
{...props}
fullWidth
/>
);
/**
* Inspired by
* - https://redux-form.com/6.4.3/examples/submitvalidation/
* - https://marmelab.com/react-admin/Actions.html#using-a-data-provider-instead-of-fetch
*/constsubmit = data =>
dataProvider(CREATE, 'things', { data: { ...data } }).catch(e => {
const payLoadKeys = Object.keys(data);
const errorKey = payLoadKeys.length === 1 ? payLoadKeys[0] : '_error';
// Here I set the error either on the key by the name of the field// if there was just 1 field in the payload.// The `Field` with the same `name` in the `form` wil have// the `helperText` shown.// When multiple fields where present in the payload, the error message is set on the _error key, making the general error visible.const errorObject = {
[errorKey]: e.message,
};
thrownewSubmissionError(errorObject);
});
constMyForm = ({ isLoading, handleSubmit, error, translate }) => (
<formonSubmit={handleSubmit(submit)}><div><div><Fieldname="email"component={renderInput}label="Email"disabled={isLoading}
/></div></div><CardActions><Buttonvariant="raised"type="submit"color="primary"disabled={isLoading}
>
{isLoading && <CircularProgresssize={25}thickness={2} />}
Signin
</Button>
{error && <strong>General error: {translate(error)}</strong>}
</CardActions></form>
);
MyForm.propTypes = {
...propTypes,
classes: PropTypes.object,
redirectTo: PropTypes.string,
};
constmapStateToProps = state => ({ isLoading: state.admin.loading > 0 });
const enhance = compose(
translate,
connect(mapStateToProps),
reduxForm({
form: 'aFormName',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.email)
errors.email = translate('ra.validation.required');
return errors;
},
})
);
exportdefaultenhance(MyForm);
If the code needs further explanation, drop a comment below and I'll try to elaborate.
I hoped to be able to do the action of the REST-request by dispatching an action with onSuccess and onFailure side effects as described here, but I couldn't get that to work together with SubmissionError
.
Solution 2:
Here one more solution from official repo. https://github.com/marmelab/react-admin/pull/871 You need to import HttpError(message, status, body) in DataProvider and throw it. Then in errorSaga parse body to redux-form structure. That's it. Enjoy.
Solution 3:
Found a working solution for react-admin 3.8.1 that seems to work well.
Here is the reference code
https://codesandbox.io/s/wy7z7q5zx5?file=/index.js:966-979
Example:
First make the helper functions as necessary.
constsleep = ms => newPromise(resolve =>setTimeout(resolve, ms));
constsimpleMemoize = fn => {
let lastArg;
let lastResult;
returnarg => {
if (arg !== lastArg) {
lastArg = arg;
lastResult = fn(arg);
}
return lastResult;
};
};
Then the actual validation code
const usernameAvailable = simpleMemoize(asyncvalue => {
if (!value) {
return"Required";
}
await sleep(400);
if (
~["john", "paul", "george", "ringo"].indexOf(value && value.toLowerCase())
) {
return"Username taken!";
}
});
Finally wire it up to your field:
const validateUserName = [required(), maxLength(10), abbrevUnique];
constUserNameInput = (props) => {
return (
<TextInputlabel="User Name"source="username"variant='outlined'validate={validateAbbrev}
></TextInput>);
}
Solution 4:
In addition to Christiaan Westerbeek's answer.
I just recreating a SimpleForm component with some of Christian's hints.
In begining i tried to extend SimpleForm with needed server-side validation functionality, but there were some issues (such as not binded context to its handleSubmitWithRedirect
method), so i just created my CustomForm to use it in every place i need.
importReact, { Children, Component } from'react';
importPropTypesfrom'prop-types';
import { reduxForm, SubmissionError } from'redux-form';
import { connect } from'react-redux';
import compose from'recompose/compose';
import { withStyles } from'@material-ui/core/styles';
import classnames from'classnames';
import { getDefaultValues, translate } from'ra-core';
importFormInputfrom'ra-ui-materialui/lib/form/FormInput';
importToolbarfrom'ra-ui-materialui/lib/form/Toolbar';
import {CREATE, UPDATE} from'react-admin';
import { showNotification as showNotificationAction } from'react-admin';
import { push as pushAction } from'react-router-redux';
import dataProvider from"../../providers/dataProvider";
conststyles = theme => ({
form: {
[theme.breakpoints.up('sm')]: {
padding: '0 1em 1em 1em',
},
[theme.breakpoints.down('xs')]: {
padding: '0 1em 5em 1em',
},
},
});
constsanitizeRestProps = ({
anyTouched,
array,
asyncValidate,
asyncValidating,
autofill,
blur,
change,
clearAsyncError,
clearFields,
clearSubmit,
clearSubmitErrors,
destroy,
dirty,
dispatch,
form,
handleSubmit,
initialize,
initialized,
initialValues,
pristine,
pure,
redirect,
reset,
resetSection,
save,
submit,
submitFailed,
submitSucceeded,
submitting,
touch,
translate,
triggerSubmit,
untouch,
valid,
validate,
...props
}) => props;
/*
* Zend validation adapted catch(e) method.
* Formatted as
* e = {
* field_name: { errorType: 'messageText' }
* }
*/constsubmit = (data, resource) => {
let actionType = data.id ? UPDATE : CREATE;
returndataProvider(actionType, resource, {data: {...data}}).catch(e => {
let errorObject = {};
for (let fieldName in e) {
let fieldErrors = e[fieldName];
errorObject[fieldName] = Object.values(fieldErrors).map(value =>`${value}\n`);
}
thrownewSubmissionError(errorObject);
});
};
exportclassCustomFormextendsComponent {
handleSubmitWithRedirect(redirect = this.props.redirect) {
returnthis.props.handleSubmit(data => {
returnsubmit(data, this.props.resource).then((result) => {
let path;
switch (redirect) {
case'create':
path = `/${this.props.resource}/create`;
break;
case'edit':
path = `/${this.props.resource}/${result.data.id}`;
break;
case'show':
path = `/${this.props.resource}/${result.data.id}/show`;
break;
default:
path = `/${this.props.resource}`;
}
this.props.dispatch(this.props.showNotification(`${this.props.resource} saved`));
returnthis.props.dispatch(this.props.push(path));
});
});
}
render() {
const {
basePath,
children,
classes = {},
className,
invalid,
pristine,
push,
record,
resource,
showNotification,
submitOnEnter,
toolbar,
version,
...rest
} = this.props;
return (
<form
// onSubmit={this.props.handleSubmit(submit)}className={classnames('simple-form', className)}
{...sanitizeRestProps(rest)}
><divclassName={classes.form}key={version}>
{Children.map(children, input => {
return (
<FormInputbasePath={basePath}input={input}record={record}resource={resource}
/>
);
})}
</div>
{toolbar &&
React.cloneElement(toolbar, {
handleSubmitWithRedirect: this.handleSubmitWithRedirect.bind(this),
invalid,
pristine,
submitOnEnter,
})}
</form>
);
}
}
CustomForm.propTypes = {
basePath: PropTypes.string,
children: PropTypes.node,
classes: PropTypes.object,
className: PropTypes.string,
defaultValue: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
handleSubmit: PropTypes.func, // passed by redux-forminvalid: PropTypes.bool,
pristine: PropTypes.bool,
push: PropTypes.func,
record: PropTypes.object,
resource: PropTypes.string,
redirect: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
save: PropTypes.func, // the handler defined in the parent, which triggers the REST submissionshowNotification: PropTypes.func,
submitOnEnter: PropTypes.bool,
toolbar: PropTypes.element,
validate: PropTypes.func,
version: PropTypes.number,
};
CustomForm.defaultProps = {
submitOnEnter: true,
toolbar: <Toolbar />,
};
const enhance = compose(
connect((state, props) => ({
initialValues: getDefaultValues(state, props),
push: pushAction,
showNotification: showNotificationAction,
})),
translate, // Must be before reduxForm so that it can be used in validationreduxForm({
form: 'record-form',
destroyOnUnmount: false,
enableReinitialize: true,
}),
withStyles(styles)
);
exportdefaultenhance(CustomForm);
For better understanding of my catch
callback: In my data provider i do something like this
...
if (response.status !== 200) {
returnPromise.reject(response);
}
return response.json().then((json => {
if (json.state === 0) {
returnPromise.reject(json.errors);
}
switch(type) {
...
}
...
}
...
Post a Comment for "How Can I Show Customized Error Messaged From Server Side Validation In React Admin Package?"