React Redux-动态创建Redux Forms
React Redux-dynamically creating Redux Forms
我正在构建一个允许用户邀请朋友的组件。
该组件的规格是它将有几个用于朋友的电子邮件和公司的输入表单,一个将添加更多输入表单的按钮,以及一个远程提交所有表单的按钮。提交表单时,每个表单中都会出现一个微调器,直到收到服务器的响应,此时,如果提交成功,则表单消失,如果有错误,则显示错误。
我坚持以下几点:为了使用 Redux Form 远程提交表单,您需要将其名称传递给提交组件。我想以编程方式创建表单。名称将是自动递增的整数,由表单管理组件创建,并作为 props 传递给子表单。但是,当我尝试导出表单并将名称引用为 this.props.name 时,出现错误:'Uncaught TypeError: Cannot read 属性 'props' of undefined'-"this"未定义。
问题:
- 我解决这个问题的方法是否有效,或者我应该在基本层面上做些不同的事情吗?
- 我该如何解决这个问题?我认为这是一个范围界定错误?
我的组件:
管理组件(创建和删除表单、提交表单等):
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
我正在构建一个允许用户邀请朋友的组件。
该组件的规格是它将有几个用于朋友的电子邮件和公司的输入表单,一个将添加更多输入表单的按钮,以及一个远程提交所有表单的按钮。提交表单时,每个表单中都会出现一个微调器,直到收到服务器的响应,此时,如果提交成功,则表单消失,如果有错误,则显示错误。
我坚持以下几点:为了使用 Redux Form 远程提交表单,您需要将其名称传递给提交组件。我想以编程方式创建表单。名称将是自动递增的整数,由表单管理组件创建,并作为 props 传递给子表单。但是,当我尝试导出表单并将名称引用为 this.props.name 时,出现错误:'Uncaught TypeError: Cannot read 属性 'props' of undefined'-"this"未定义。
问题:
- 我解决这个问题的方法是否有效,或者我应该在基本层面上做些不同的事情吗?
- 我该如何解决这个问题?我认为这是一个范围界定错误?
我的组件:
管理组件(创建和删除表单、提交表单等):
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