0

我有 2 个不同的视图。它们都使用相同的形式。表单存在于模态中。该表单在第一个视图(创建模式)中完美运行。

现在我想为(Edit Mode)添加另一个。唯一的区别是编辑模式需要实际项目的值(在我们的例子中是用户)

一般来说,我了解高阶组件的工作原理。但我目前的配置Redux, Formik and Yup分别用于获取数据、表单处理和验证。

我在网上尝试了一些示例,但没有一个适合我的情况。这就是问题所在。我应该将多少功能转移到高阶组件?

功能一:实际形式应该存在的地方。组件,还是 HOC?Code Below:

我应该在 HOC 中移动它吗?因为它是完全相同的形式。如果是,为什么?

功能 2:我正在使用 Yup 进行表单验证。我已将此功能移至 HOC,但现在,我无法将 ValidatioSchema 传递给表单组件。Code Below

功能3:如何处理Modal。模态有点棘手。我决定在每个单独的屏幕中单独使用它。这是正确的还是我应该将它包含在 HOC 中?

我的 HOC:

import React, { Component } from 'react';
import { withFormik } from 'formik';
import PropTypes from 'prop-types';
import * as Yup from 'yup';

import { createUser, updateUser } from './service';
import { listGroups } from '../groups/service';

const AddEditUserHOCForm = WrappedComponent => {
  class ViewUser extends Component {
    static propTypes = {
      user: PropTypes.object,
      onCancel: PropTypes.func,
      onSave: PropTypes.func,
      status: PropTypes.string,
      values: PropTypes.object,
      errors: PropTypes.object,
      isSubmitting: PropTypes.bool,
      handleChange: PropTypes.func,
      handleSubmit: PropTypes.func,
      setStatus: PropTypes.func
    };

    static defaultProps = {
      user: {},
      onCancel: () => { },
      onSave: () => { },
      status: '',
      values: {},
      errors: {},
      isSubmitting: false,
      handleChange: () => { },
      handleSubmit: () => { },
      setStatus: () => { }
    };

    state = {
      groups: [],
      isRetrievingData: false
    };

    componentDidMount() {
      this.setState({
        isRetrievingData: true
      });
      return new Promise([listGroups()])
        .then(values => values.map(res => res.data))
        .then(([groups]) => {
          this.setState({
            isRetrievingData: false,
            groups
          });
        })
        .catch(({ message = 'Could not retrieve data from server.' }) => {
          this.setState({
            isRetrievingData: false
          });
          this.props.setStatus(message);
        });
    }

    handleCancel = () => {
      const { onCancel } = this.props;

      if (onCancel) {
        onCancel();
      }
    };

    render() {
      return (
        <WrappedComponent
          {...this.props}
          {...this.state}
          onSubmit={this.handleSubmit}
          onCancel={this.handleCanel}
        />
      );
    }
  }

  const UserValidationSchema = Yup.object().shape({
    username: Yup.string('Provide a Username').required('Username is Required'),
    password: Yup.string().email('Provide a Valid email Address'),
    confirmPassword: Yup.string('Enter your password again')
      .required('Password Confirmation is Required')
      .oneOf([Yup.ref('password')], 'Passwords do not match')
  });

  const NewUserFormHOC = withFormik({
    mapPropsToValues: ({ user }) => ({ ...user }),
    UserValidationSchema,

    handleSubmit: (values, { props, setSubmitting, setStatus }) => {
      const saveUser = values.username ? updateUser : createUser;
      return saveUser(values)
        .then(() => {
          setSubmitting(false);
          setStatus('');
          props.onSave(values);
          props.onCancel();
        })
        .catch(({ message, response: { data } }) => {
          setSubmitting(false);
          setStatus(data || message);
        });
    },

    displayName: 'ViewUser'
  })(ViewUser);
  return NewUserFormHOC;
};

export default AddEditUserHOCForm;

这是我的表单组件:

import React, { Component, Fragment } from 'react';
import { Formik, Form, Field } from 'formik';
import PropTypes from 'prop-types';
import AddEditUserHOCForm from './components/AddEditUserHOC'

import LensesSelect from './data/ReactSelectComponent';
import formPropTypes from '../constants/formPropTypes';

import { listGroups } from '../groups/service';

class UserCreateForm extends Component {
  static propTypes = {
    ...formPropTypes,
    username: PropTypes.string,
    email: PropTypes.string,
    password: PropTypes.string,
    confirmPassword: PropTypes.string,
    groupSelect: PropTypes.func
  };

  static defaultProps = {
    email: ''
  };

  state = {
    type: 'password',
    groups: []
  };

  componentDidMount() {
    this.fetchListGroups();
  }

  fetchListGroups = () => {
    listGroups().then(({ data }) => {
      this.setState({ groups: data });
    });
  };

  mapListGroupToSelect = () => {
    const { groups } = this.state;
    return groups.map(group => ({
      label: group.name,
      value: group.name
    }));
  };

  togglePasswordMask = e => {
    const { type } = this.state;
    e.preventDefault();
    this.setState(prevState => ({
      passwordIsMasked: !prevState.passwordIsMasked,
      type: type === 'password' ? 'input' : 'password'
    }));
  };

  selectOnChangeCallback = response => {
    // eslint-disable-next-line no-console
    console.log('selectOnChangeCallback', response);
  };

  render() {
    const { type } = this.state;
    const selectData = this.mapListGroupToSelect();
    return (
      <Fragment>
        <Formik
          initialValues={{
            username: '',
            email: '',
            password: '',
            confirmPassword: ''
          }}
          // validationSchema={createUserValidationSchema}
          onSubmit={values => {
            // same shape as initial values
            console.log(values);
          }}
        >
          {({ errors, touched }) => (
            <Form>
              <div className="my-3">
                <label>
                  Username <span className="text-danger">*</span>
                </label>
                <Field name="username" type="text" className="form-control rounded-0" />
                {errors.username && touched.username ? (
                  <div className="text-danger">{errors.username}</div>
                ) : null}
              </div>
              <div className="my-3">
                <label>email</label>
                <Field name="email" type="email" className="form-control rounded-0" />
                {errors.email && touched.email ? (
                  <div className="text-danger">{errors.email}</div>
                ) : null}
              </div>
              <div className="my-3">
                <label>
                  Password <span className="text-danger">*</span>
                </label>
                <div className="d-flex align-items-center">
                  <Field type={type} name="password" className="form-control rounded-0 mr-2" />
                  <span
                    className={type === 'password' ? 'fa fa-eye fa-lg' : 'fa fa-eye-slash fa-lg'}
                    onClick={this.togglePasswordMask}
                  />
                </div>
                {errors.password && touched.password ? (
                  <div className="text-danger">{errors.password}</div>
                ) : null}
              </div>
              <div className="my-3">
                <label>
                  Confirm Password <span className="text-danger">*</span>
                </label>
                <Field name="confirmPassword" type={type} className="form-control rounded-0" />
                {errors.confirmPassword && touched.confirmPassword ? (
                  <div className="text-danger">{errors.confirmPassword}</div>
                ) : null}
              </div>
              <div className="my-3">
                <label>
                  Select Group <span className="text-danger">*</span>
                </label>
                <ReactSelectComponent
                  isMulti={false}
                  options={selectData}
                  onChangeCallback={this.selectOnChangeCallback}
                />
              </div>
              <button type="submit" className="btn btn-primary rounded-0 float-right my-5">
                <span className="mx-2">Create User</span>
              </button>
            </Form>
          )}
        </Formik>
      </Fragment>
    );
  }
}

export default AddEditUserHOCForm(UserCreateForm);

我知道这是很多代码需要通过。但我不知所措。真的不知道我应该在哪里包括什么。

对我来说,我需要 form(Formik) 和 Yup,以及 HOC 上的 Redux。然后根据视图添加数据。请我真的需要一些指导和一些例子。谢谢你。

4

1 回答 1

0

React 很棒,但它给了你太多的自由。在我看来,您必须将 SOLID 原则应用于您的组件。

正如您可以通过网络阅读的那样,您的组件应该只做一件事并且做得很好。

所以。如果我要这样做,我会将表单放在一个文件夹中,其中一个文件代表其表示配置,另一个文件代表业务逻辑。您可以将第二部分作为一个 HOC 来完成。

比方说,UserForm.js,withUserValidations,withUserActions。然后把整个东西包起来。

然后你只需要在你想要的地方使用它。我希望它有所帮助。

于 2019-02-07T23:00:53.433 回答