React Redux — динамическое создание Redux Forms

Я создаю компонент, который позволит пользователям приглашать своих друзей.

Спецификация компонента заключается в том, что он будет иметь несколько форм ввода для электронных писем и компаний их друзей, кнопку, которая будет добавлять дополнительные формы ввода, и кнопку, которая удаленно отправляет все формы. При отправке форм в каждой форме появляется счетчик до тех пор, пока не будет получен ответ от сервера, после чего, если отправка прошла успешно, форма исчезает, а если произошла ошибка, отображается ошибка.

Я застрял на следующем: чтобы отправить форму удаленно с помощью Redux Form, вам нужно передать ее имя отправляющему компоненту. Я хочу создавать формы программно. Имена будут автоматически увеличивающимися целыми числами, созданными компонентом управления формами и переданными дочерним формам в качестве реквизита. Однако, когда я пытаюсь экспортировать форму, ссылаясь на имя как this.props.name, я получаю сообщение об ошибке: «Uncaught TypeError: Cannot read property '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);

person Boris K    schedule 21.11.2017    source источник


Ответы (1)


Ответ: РТФМ. 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)

проверить.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
person Boris K    schedule 22.11.2017