如何在React Admin包中显示来自服务器端验证的自定义错误消息?

时间:2018-05-08 15:24:18

标签: javascript reactjs react-admin

有没有办法使用https://github.com/marmelab/react-admin包执行服务器端表单验证?

这是AdminCreate Component的代码。它向api发送创建请求。如果一切正常,Api将返回验证错误,状态码为422或状态码为200。

    export class AdminCreate extends Component {
  render() {
    return <Create {...this.props}>
        <SimpleForm>
          <TextInput source="name"  type="text" />
          <TextInput source="email" type="email"/>
          <TextInput source="password" type="password"/>
          <TextInput source="password_confirmation" type="password"/>
          <TextInput source="phone" type="tel"/>    
        </SimpleForm>
    </Create>;

}
}

所以问题是,如何从服务器发送的错误对象中分别显示每个字段的错误?以下是错误对象的示例:

{
errors: {name: "The name is required", email: "The email is required"},
message: "invalid data"
}

提前谢谢!

class SimpleForm extends Component {
    handleSubmitWithRedirect = (redirect = this.props.redirect) =>
        this.props.handleSubmit(data => {
          dataProvider(CREATE, 'admins', { data: { ...data } }).catch(e => {
            throw new SubmissionError(e.body.errors)
          }).then(/* Here must be redirection logic i think  */);
        });

    render() {
        const {
            basePath,
            children,
            classes = {},
            className,
            invalid,
            pristine,
            record,
            resource,
            submitOnEnter,
            toolbar,
            version,
            ...rest
        } = this.props;

        return (
            <form
                className={classnames('simple-form', className)}
                {...sanitizeRestProps(rest)}
            >
                <div className={classes.form} key={version}>
                    {Children.map(children, input => (
                        <FormInput
                            basePath={basePath}
                            input={input}
                            record={record}
                            resource={resource}
                        />
                    ))}
                </div>
                {toolbar &&
                    React.cloneElement(toolbar, {
                        handleSubmitWithRedirect: this.handleSubmitWithRedirect,
                        invalid,
                        pristine,
                        submitOnEnter,
                    })}
            </form>
        );
    }
}

现在我有以下代码,它显示验证错误。但问题是,我无法在成功后执行重定向。有什么想法吗?

4 个答案:

答案 0 :(得分:5)

如果您正在使用SimpleForm,则可以asyncValidateasyncBlurFields一起使用form。我没有使用SimpleForm,所以我可以告诉你这一点。

我使用了一个简单的import React from 'react'; import PropTypes from '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'; import Button from 'material-ui/Button'; import TextField from 'material-ui/TextField'; import { CircularProgress } from 'material-ui/Progress'; import { CREATE, translate } from 'ra-core'; import { dataProvider } from '../../providers'; // <-- Make sure to import yours! const renderInput = ({ meta: { touched, error } = {}, input: { ...inputProps }, ...props }) => ( <TextField error={!!(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 */ const submit = 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, }; throw new SubmissionError(errorObject); }); const MyForm = ({ isLoading, handleSubmit, error, translate }) => ( <form onSubmit={handleSubmit(submit)}> <div> <div> <Field name="email" component={renderInput} label="Email" disabled={isLoading} /> </div> </div> <CardActions> <Button variant="raised" type="submit" color="primary" disabled={isLoading} > {isLoading && <CircularProgress size={25} thickness={2} />} Signin </Button> {error && <strong>General error: {translate(error)}</strong>} </CardActions> </form> ); MyForm.propTypes = { ...propTypes, classes: PropTypes.object, redirectTo: PropTypes.string, }; const mapStateToProps = 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; }, }) ); export default enhance(MyForm); 。您也可以在那里使用服务器端验证。这是我如何做到的。一个完整而有效的例子。

SubmissionError

如果代码需要进一步解释,请在下面发表评论,我会尝试详细说明。

我希望能够通过调用带有onSuccess和onFailure副作用的动作来执行REST请求的操作,如a comment in issue 97所述,但我无法与here一起工作。 1}}。

答案 1 :(得分:1)

这里是官方仓库中的另一种解决方案。 https://github.com/marmelab/react-admin/pull/871 您需要在DataProvider中导入HttpError(消息,状态,正文)并将其抛出。 然后在errorSaga中将正文解析为redux-form结构。 而已。 享受。

答案 2 :(得分:1)

找到了适用于 react-admin 3.8.1 的有效解决方案。

这是参考代码

https://codesandbox.io/s/wy7z7q5zx5?file=/index.js:966-979

示例:

首先根据需要使助手发挥作用。

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const simpleMemoize = fn => {
  let lastArg;
  let lastResult;
  return arg => {
    if (arg !== lastArg) {
      lastArg = arg;
      lastResult = fn(arg);
    }
    return lastResult;
  };
};

然后输入实际的验证码

const usernameAvailable = simpleMemoize(async value => {
  if (!value) {
    return "Required";
  }
  await sleep(400);
  if (
    ~["john", "paul", "george", "ringo"].indexOf(value && value.toLowerCase())
  ) {
    return "Username taken!";
  }
});

最后将其连接到您的字段:

const validateUserName = [required(),maxLength(10),abbrevUnique];

const UserNameInput = (props) => {
    return (
        <TextInput
            label="User Name"
            source="username"
            variant='outlined'
            validate={validateAbbrev}
        >
        </TextInput>);
}

答案 3 :(得分:0)

除了 Christiaan Westerbeek 的回答。 我只是用一些基督徒的提示重新创建一个SimpleForm组件。 在开始时我尝试使用所需的服务器端验证功能扩展SimpleForm,但是存在一些问题(例如其handleSubmitWithRedirect方法没有绑定上下文),所以我创建了我的CustomForm以在我需要的每个地方使用它

import React, { Children, Component } from 'react';
import PropTypes from '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';
import FormInput from 'ra-ui-materialui/lib/form/FormInput';
import Toolbar  from '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";

const styles = theme => ({
    form: {
        [theme.breakpoints.up('sm')]: {
            padding: '0 1em 1em 1em',
        },
        [theme.breakpoints.down('xs')]: {
            padding: '0 1em 5em 1em',
        },
    },
});

const sanitizeRestProps = ({
   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' }
 * }
 */
const submit = (data, resource) => {
    let actionType = data.id ? UPDATE : CREATE;

    return dataProvider(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`);
        }

        throw new SubmissionError(errorObject);
    });
};

export class CustomForm extends Component {
    handleSubmitWithRedirect(redirect = this.props.redirect) {
        return this.props.handleSubmit(data => {
            return submit(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`));

                return this.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)}
            >
                <div className={classes.form} key={version}>
                    {Children.map(children, input => {
                        return (
                            <FormInput
                                basePath={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-form
    invalid: 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 submission
    showNotification: 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 validation
    reduxForm({
        form: 'record-form',
        destroyOnUnmount: false,
        enableReinitialize: true,
    }),
    withStyles(styles)
);

export default enhance(CustomForm);

为了更好地理解我的catch回调:在我的数据提供程序中,我做了类似这样的事情

...
    if (response.status !== 200) {
         return Promise.reject(response);
    }

    return response.json().then((json => {

        if (json.state === 0) {
            return Promise.reject(json.errors);
        }

        switch(type) {
        ...
        }
    ...
    }
...