React Redux-动态创建Redux Forms

React Redux-dynamically creating Redux Forms

我正在构建一个允许用户邀请朋友的组件。

该组件的规格是它将有几个用于朋友的电子邮件和公司的输入表单,一个将添加更多输入表单的按钮,以及一个远程提交所有表单的按钮。提交表单时,每个表单中都会出现一个微调器,直到收到服务器的响应,此时,如果提交成功,则表单消失,如果有错误,则显示错误。

我坚持以下几点:为了使用 Redux Form 远程提交表单,您需要将其名称传递给提交组件。我想以编程方式创建表单。名称将是自动递增的整数,由表单管理组件创建,并作为 props 传递给子表单。但是,当我尝试导出表单并将名称引用为 this.props.name 时,出现错误:'Uncaught TypeError: Cannot read 属性 'props' of undefined'-"this"未定义。

问题:

  1. 我解决这个问题的方法是否有效,或者我应该在基本层面上做些不同的事情吗?
  2. 我该如何解决这个问题?我认为这是一个范围界定错误?

我的组件:

管理组件(创建和删除表单、提交表单等):

import React, { Component } from 'react';
import { connect } from 'react-redux'
import { submit } from 'redux-form'
import * as actions from '../../actions';
import InviteForm from './inviteForm';

class InvitationFormManager extends Component {

  const buildForms = (length) =>{
    for (let i = 0; i< length; i++)
        {
          this.setState({forms:[...this.state.forms, <InviteForm key={i} name={i}>]};
        }
  }

  const addForm = () =>{
    this.setState({forms:[...this.state.forms, <InviteForm key={(this.state.forms.length + 1)} name={(this.state.forms.length + 1)}>]});
  }

  const formSubmit = (form) =>
  {
    dispatch(submit(form.name))
    .then(this.setState({
      forms: this.state.forms.filter(f => f.name !== form.name)
    }))
  }
  const submitForms = (){
    for(let form of this.state.forms){formSubmit(form)}
  }

  constructor(props) {
      super(props);
      this.state = {forms:[]};
  }

  componentWillMount(){
    buildForms(3)
  }

  render() {
    return (<div>
              <h5 className="display-6 text-center">Invite your team</h5>
              {this.state.forms}
              <br />
              <button
                type="button"
                className="btn btn-primary"
                onClick={submitForms}
              >
                Invite
              </button>
              <button
                type="button"
                className="btn btn-primary"
                onClick={addForm}
              >
                +
              </button>
          </div>
      );
    }
}


export default connect(actions)(InvitationFormManager)

表单组件:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import * as actions from '../../actions';
import { Link } from 'react-router';

const renderField = ({
    input,
    label,
    type,
    meta: { touched, error, warning }
}) => (
    <fieldset className="form-group">
        <label htmlFor={input.name}>{label}</label>
        <input className="form-control" {...input} type={type} />
        {touched && error && <span className="text-danger">{error}</span>}
    </fieldset>
);

class InviteForm extends Component {
    constructor(props) {
        super(props);
        this.name = this.name.bind(this);
    }

    handleFormSubmit(props) {
        this.props.sendInvitation(props);
    }

    render() {
        if (this.props.submitting) {
            return (
                <div className="dashboard loading">
                    <Spinner name="chasing-dots" />
                </div>
            );
        }
        const { formName, handleSubmit } = this.props;
        return (
            <div className="form-container text-center">
                <form
                    className="form-inline"
                    onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
                    <div className="form-group">
                        <Field
                            name="email"
                            component={renderField}
                            type="email"
                            label="Email"
                        />
                        <Field
                            name="company"
                            component={renderField}
                            type="text"
                            label="Company"
                        />
                    </div>
                </form>
                <div>
                    {this.props.errorMessage &&
                        this.props.errorMessage.invited && (
                            <div className="error-container">
                                Oops! {this.props.errorMessage.invited}
                            </div>
                        )}
                </div>
            </div>
        );
    }
}
function validate(values) {
    let errors = {};

    if (values.password !== values.password_confirmation) {
        errors.password = "Password and password confirmation don't match!";
    }

    return errors;
}
function mapStateToProps(state) {
    return {
        errorMessage: state.invite.error,
        submitting: state.invite.submitting
    };
}

InviteForm = reduxForm({
    form: this.props.name,
    validate
})(InviteForm);
export default connect(mapStateToProps, actions)(InviteForm);

答案是,RTFM。 Redux Form 具有与 FieldArrays 一样的功能。

Redux Form 7.0.4 的示例在这里:https://redux-form.com/7.0.4/examples/fieldarrays/

以防他们以后移动它,这里是:

FieldArraysForm.js

import React from 'react'
import { Field, FieldArray, reduxForm } from 'redux-form'
import validate from './validate'

const renderField = ({ input, label, type, meta: { touched, error } }) =>
  <div>
    <label>
      {label}
    </label>
    <div>
      <input {...input} type={type} placeholder={label} />
      {touched &&
        error &&
        <span>
          {error}
        </span>}
    </div>
  </div>

const renderHobbies = ({ fields, meta: { error } }) =>
  <ul>
    <li>
      <button type="button" onClick={() => fields.push()}>
        Add Hobby
      </button>
    </li>
    {fields.map((hobby, index) =>
      <li key={index}>
        <button
          type="button"
          title="Remove Hobby"
          onClick={() => fields.remove(index)}
        />
        <Field
          name={hobby}
          type="text"
          component={renderField}
          label={`Hobby #${index + 1}`}
        />
      </li>
    )}
    {error &&
      <li className="error">
        {error}
      </li>}
  </ul>

const renderMembers = ({ fields, meta: { error, submitFailed } }) =>
  <ul>
    <li>
      <button type="button" onClick={() => fields.push({})}>
        Add Member
      </button>
      {submitFailed &&
        error &&
        <span>
          {error}
        </span>}
    </li>
    {fields.map((member, index) =>
      <li key={index}>
        <button
          type="button"
          title="Remove Member"
          onClick={() => fields.remove(index)}
        />
        <h4>
          Member #{index + 1}
        </h4>
        <Field
          name={`${member}.firstName`}
          type="text"
          component={renderField}
          label="First Name"
        />
        <Field
          name={`${member}.lastName`}
          type="text"
          component={renderField}
          label="Last Name"
        />
        <FieldArray name={`${member}.hobbies`} component={renderHobbies} />
      </li>
    )}
  </ul>

const FieldArraysForm = props => {
  const { handleSubmit, pristine, reset, submitting } = props
  return (
    <form onSubmit={handleSubmit}>
      <Field
        name="clubName"
        type="text"
        component={renderField}
        label="Club Name"
      />
      <FieldArray name="members" component={renderMembers} />
      <div>
        <button type="submit" disabled={submitting}>
          Submit
        </button>
        <button type="button" disabled={pristine || submitting} onClick={reset}>
          Clear Values
        </button>
      </div>
    </form>
  )
}

export default reduxForm({
  form: 'fieldArrays', // a unique identifier for this form
  validate
})(FieldArraysForm)

validate.js

const validate = values => {
  const errors = {}
  if (!values.clubName) {
    errors.clubName = 'Required'
  }
  if (!values.members || !values.members.length) {
    errors.members = { _error: 'At least one member must be entered' }
  } else {
    const membersArrayErrors = []
    values.members.forEach((member, memberIndex) => {
      const memberErrors = {}
      if (!member || !member.firstName) {
        memberErrors.firstName = 'Required'
        membersArrayErrors[memberIndex] = memberErrors
      }
      if (!member || !member.lastName) {
        memberErrors.lastName = 'Required'
        membersArrayErrors[memberIndex] = memberErrors
      }
      if (member && member.hobbies && member.hobbies.length) {
        const hobbyArrayErrors = []
        member.hobbies.forEach((hobby, hobbyIndex) => {
          if (!hobby || !hobby.length) {
            hobbyArrayErrors[hobbyIndex] = 'Required'
          }
        })
        if (hobbyArrayErrors.length) {
          memberErrors.hobbies = hobbyArrayErrors
          membersArrayErrors[memberIndex] = memberErrors
        }
        if (member.hobbies.length > 5) {
          if (!memberErrors.hobbies) {
            memberErrors.hobbies = []
          }
          memberErrors.hobbies._error = 'No more than five hobbies allowed'
          membersArrayErrors[memberIndex] = memberErrors
        }
      }
    })
    if (membersArrayErrors.length) {
      errors.members = membersArrayErrors
    }
  }
  return errors
}

export default validate