我收到两个不同的错误。
第一个是当我通过导出reduxForm方法连接我的组件/ stateToProps / reduxForm时。它说我失败的道具类型是未定义的。
例如:
function mapStateToProps(state) {
const { account, priceBook } = state;
const accountId = account.id;
return { accountId, priceBook };
}
export default reduxForm({
form: 'createOrder',
destroyOnUnmount: false,
validate,
}, mapStateToProps, {
getRevShareAction: getRevShare,
getPriceBookAction: getPriceBook,
})(NewOrderFormFour);
Warning: Failed prop type: The prop `getPriceBookAction` is marked as required in `NewOrderFormFour`, but its value is `undefined`.
所以为了纠正这个问题,我将reduxForm分开并使用react-redux的connect方法连接道具。
NewOrderFormFour = reduxForm({
form: 'createOrder',
destroyOnUnmount: false,
validate,
});
export default connect(mapStateToProps, {
getRevShareAction: getRevShare,
getPriceBookAction: getPriceBook,
})(NewOrderFormFour);
这将返回错误说:
Invariant Violation: Component(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
如果我在第二个实例中注释掉reduxForm,并在组件中呈现它呈现的简单<div>
标记。我不确定是什么导致了这个问题。我已经查看了组件中的任何拼写错误或潜在的错误,似乎没有什么意义。
以下是整个组件:
import React, { PropTypes } from 'react';
import { reduxForm, Form, Field } from 'redux-form';
import { connect } from 'react-redux';
import { getClassName } from '../../utils/forms';
import { storeWithExpiration, getOrderTotalCost } from '../../utils/common';
import { getRevShare, getPriceBook } from '../../actions/pricing';
import DateTimePicker from 'react-widgets/lib/DateTimePicker';
import moment from 'moment';
import momentLocalizer from 'react-widgets/lib/localizers/moment';
momentLocalizer(moment);
import ConfirmOrderDialog from './ConfirmOrderDialog';
import PriceBookTableContainer from '../pricing/PriceBookTableContainer';
const formats = [
'MMM d yyyy',
'MMM d yy',
'd',
];
export class NewOrderFormFour extends React.Component {
static propTypes = {
accountId: PropTypes.number.isRequired,
companyData: PropTypes.object,
cancelOrderFunction: PropTypes.func.isRequired,
closeDialogFunction: PropTypes.func.isRequired,
dataSets: PropTypes.object.isRequired,
dialog: PropTypes.object.isRequired,
escapeForm: PropTypes.func.isRequired,
fetchFieldsetsByIdFunction: PropTypes.func.isRequired,
formValues: PropTypes.object,
fieldSets: PropTypes.object.isRequired,
getPriceBookAction: PropTypes.func.isRequired,
getRevShareAction: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
openDialogFunction: PropTypes.func.isRequired,
platformMap: PropTypes.object.isRequired,
previousPageFunction: PropTypes.func.isRequired,
priceBook: PropTypes.object.isRequired,
providerOrderNumber: PropTypes.string.isRequired,
providerCustomerNumber: PropTypes.string.isRequired,
renderInput: PropTypes.func.isRequired,
renderCompanyInfo: PropTypes.func.isRequired,
submitFunction: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
useCaseMap: PropTypes.object.isRequired,
};
createOrderFunction = (params) => {
const { openDialogFunction, submitFunction } = this.props;
openDialogFunction('confirmOrderDialog');
submitFunction(params);
};
handleStartDayChange = (param, startDay) => {
const { formValues: { endDay } } = this.props;
const endDayDate = new Date(moment(param).add(1, 'y').format());
if (endDayDate) {
// endDay.value = endDayDate;
endDay.onChange(endDayDate);
}
return startDay.onChange(param);
}
handleEndDayChange = (param, endDay) => {
return endDay.onChange(param);
}
// Field manipulation is due to the fact that the calendar picker
// requires a date object, but react currently doesn't support
// passing objects as form values
openConfirmationDialog = () => {
const { openDialogFunction } = this.props;
let { formValues: { startDay, endDay } } = this.props;
const startDayString = JSON.stringify(startDay);
startDay = startDayString;
const endDayString = JSON.stringify(endDay);
endDay = endDayString;
openDialogFunction('confirmOrderDialog');
}
render() {
const {
accountId,
dataSets,
dialog,
escapeForm,
fieldSets,
formValues,
formValues: {
platformCode,
numberOfSeats,
numberOfRecords,
},
handleSubmit,
platformMap,
priceBook,
useCaseMap,
renderInput,
renderCompanyInfo,
submitting,
closeDialogFunction,
previousPageFunction,
} = this.props;
const isCRMTable = platformCode === 'SFDC';
const pricebookData = isCRMTable ? priceBook.get('crmList') : priceBook.get('maList');
const selectedPbData = pricebookData ? pricebookData.map(value => {
return {
floor: value.get('floor'),
min: value.get('min'),
max: value.get('max'),
};
}).toArray() : null;
// If the user opens confirm dialog then cancels, we need to convert start and and dates
// from a string back to a date object
// Don't convert if dialog open
const dialogIsOpen = dialog.get('show');
let { formValues: { startDay, endDay } } = this.props;
if (!dialogIsOpen) {
if (typeof startDay === 'string' && startDay.length > 0) {
startDay = new Date(JSON.parse(startDay));
}
if (typeof endDay === 'string' && endDay.length > 0) {
endDay = new Date(JSON.parse(endDay));
}
}
return (
<div className="modal-wrap">
<div className="container">
<div className="card card-block">
<span className="pull-right">
<button
type="button"
className="btn btn-secondary escape"
onClick={ () => escapeForm() }
>
<i className='fa fa-times'></i>
</button>
</span>
{ renderCompanyInfo() }
<ul className="nav nav-tabs">
<li className="nav-item">
<a href="#" className="nav-link active">Select Existing SKU</a>
</li>
{/* <li className="nav-item">
<a href="#" className="nav-link">Clone & Edit an Existing Order</a>
</li> */}
</ul>
<Form id="createOrder" onSubmit={ handleSubmit(this.openConfirmationDialog.bind(this))}>
<fieldset>
<div className="card card-block order-card">
<h3 className="card-title">
</h3>
<p className="card-text">
<strong>
Select a List, and then a SKU
</strong>
</p>
<div className="form-group">
<div className="form-group row">
<div className="col-md-6">
<div className="form-group">
<Field
name="listId"
component={ this.renderGenericField }
label="List"
/>
</div>
</div>
<div className="col-md-6">
<span className="col-sm-10 pull-right">
<button
type="button"
className="btn btn-secondary btn-back"
onClick={ previousPageFunction }
>
Back
</button>
<button
type="submit"
className="btn btn-primary"
disabled={ submitting }
>
Place Order
</button>
</span>
</div>
</div>
</div>
</div>
</fieldset>
</Form>
<PriceBookTableContainer
accountId={ accountId }
dialog={ dialog }
isDAdmin={ false }
isCRM={ isCRMTable }
isPAdmin={ false }
tableContents={ isCRMTable ? priceBook.get('crmList') : priceBook.get('maList') }
revShare={ priceBook.get('revShare') }
openDialogFunction={ () => {
} }
closeDialogFunction={ () => {
} }
updatePriceAction={ () => {
} }
/>
</div>
</div>
{ dialog.id === 'confirmOrderDialog' && dialog.show ?
<ConfirmOrderDialog
dataSets={ dataSets }
dialog={ dialog }
formValues={ formValues }
fieldSets={ fieldSets }
selectedPbData={ selectedPbData }
platformMap={ platformMap }
useCaseMap={ useCaseMap }
closeDialogFunction={ closeDialogFunction }
submitOrderFunction={ this.createOrderFunction }
/> : null }
</div>
);
}
renderGenericField = ({ input, label, meta: { touched, error } }) => {
return (
<div className={ getClassName(touched, error) }>
<label className="form-control-label row">
{label}:
{ touched && error && <span> | {error} </span> }
</label>
{ this.renderSpecialInput(label, input) }
</div>
);
}
renderSpecialInput = (label, input) => {
if (label === 'List') {
return this.renderListIdSelect(input);
}
else if (label === 'SKU') {
return this.renderSkuIdSelect(input);
}
else if (label === 'Start date') {
return this.renderDateTimeInput(
this.handleStartDayChange,
((typeof input === 'string') ? null : input)
);
}
else if (label === 'End date') {
return this.renderDateTimeInput(
this.handleEndDayChange,
((typeof input === 'string') ? null : input)
);
}
return null;
}
renderDateTimeInput = (changeMethod, value) => {
const date = new Date();
return (
<div className="form-group row">
<div className="col-sm-10">
<DateTimePicker
time={ false }
min={ date }
parse={ formats }
onChange={ param => changeMethod(param) }
value={ value }
/>
</div>
</div>
);
}
renderListIdSelect = (input) => {
const { fetchFieldsetsByIdFunction, dataSets } = this.props;
return (
<select
{...input}
onChange={(event) => {
input.onChange(event);
const dataset = JSON.parse(event.target.value);
fetchFieldsetsByIdFunction(dataset.id,
'SKU',
storeWithExpiration.get('token'));
}}
className="form-control form-control-lg form-control-success"
autoFocus
>
<option value="">< Please Select ></option>
{ dataSets.size > 0 ?
dataSets.valueSeq().map(this.renderDataSetItem) : null }
</select>
);
}
renderSkuIdSelect = (input) => {
const { fieldSets } = this.props;
return (
<select
{...input}
className="form-control form-control-lg form-control-success"
value={input || ''}
>
<option value="">< Please Select ></option>
{ fieldSets.size > 0 ?
fieldSets.valueSeq().map(this.renderItem)
:
<option value="-1">No SKU's found</option> }
</select>
);
}
renderDataSetItem = (item) => {
// Only render lists that are live (pending lists don't have SKUs)
if (item.status !== 0) {
return this.renderItem(item);
}
return null;
}
renderItem = (item) => {
return (
<option key={ item.id } value={ JSON.stringify(item) }>{ item.name }</option>
);
}
componentWillMount = () => {
const {
accountId,
getRevShareAction,
getPriceBookAction } = this.props;
let { formValues: { startDay, endDay } } = this.props;
const token = storeWithExpiration.get('token');
getPriceBookAction(accountId, token);
getRevShareAction(accountId, token);
startDay = null;
endDay = null;
}
}
const validate = values => {
const isCRM = values.platformCode === 'SFDC';
const today = new Date(moment().set('hour', 0).set('minute', 0).set('second', 0).format());
const onMonthFromNow = new Date(moment(today).add(1, 'M').format());
let oneMonthFromStartDate = onMonthFromNow;
if (values.startDay) {
oneMonthFromStartDate = new Date(moment(values.startDay).add(1, 'M').format());
}
const errors = {};
if (!values.listId || values.listId === '') {
errors.listId = 'Required';
}
if (!values.skuId || values.skuId === '') {
errors.skuId = 'Required';
}
else if (values.skuId === '-1') {
errors.skuId = 'Please Select a List With a SKU and Try Again';
}
// Credits
if (!values.recordCredits || values.recordCredits === '') {
errors.recordCredits = 'Required';
}
else if (isNaN(Number(values.recordCredits))) {
errors.recordCredits = 'Must be a number';
}
else if (Number(values.recordCredits) < 1) {
errors.recordCredits = 'Must be greater than 0';
}
else if (parseInt(values.recordCredits, 10) !== Number(values.recordCredits)) {
errors.recordCredits = 'Should be an integer';
}
// Number of seats
// Nested if statements seem to break validate function. Using workaround
if (!isCRM) {
// Don't validate Number of seats
}
else if (!values.numberOfSeats || values.numberOfSeats === '') {
errors.numberOfSeats = 'Required';
}
else if (isNaN(Number(values.numberOfSeats))) {
errors.numberOfSeats = 'Must be a number';
}
else if (Number(values.numberOfSeats) < 1) {
errors.numberOfSeats = 'Must be greater than 0';
}
else if (parseInt(values.numberOfSeats, 10) !== Number(values.numberOfSeats)) {
errors.numberOfSeats = 'Should be an integer';
}
else if (parseInt(values.numberOfSeats, 10) > 1000) {
errors.numberOfSeats = 'Please contact Datarista for orders over 1000';
}
// Number of records
if (isCRM) {
// Don't validate Number of records
}
else if (!isCRM && !values.numberOfRecords || values.numberOfRecords === '') {
errors.numberOfRecords = 'Required';
}
else if (isNaN(Number(values.numberOfRecords))) {
errors.numberOfRecords = 'Must be a number';
}
else if (Number(values.numberOfRecords) < 1) {
errors.numberOfRecords = 'Must be greater than 0';
}
else if (parseInt(values.numberOfRecords, 10) !== Number(values.numberOfRecords)) {
errors.numberOfRecords = 'Should be an integer';
}
else if (parseInt(values.numberOfRecords, 10) > 50000) {
errors.numberOfRecords = 'Please contact Datarista for orders over 50,000k';
}
// Start and end times
if (values.endDay === null || values.endDay === undefined) {
errors.endDay = 'Required';
}
else if (values.endDay < onMonthFromNow) {
errors.endDay = '1 month Minimum Contract Required';
}
else if (values.endDay < oneMonthFromStartDate) {
errors.endDay = '1 month Minimum Contract Required';
}
if (values.startDay === null || values.startDay === undefined) {
errors.startDay = 'Required';
}
else if (values.startDay < today) {
errors.startDay = 'Start date can\'t be before today';
}
else if (values.startDay >= values.endDay) {
errors.startDay = 'Must preceed end date';
errors.endDay = 'Must follow start date';
}
return errors;
};
function mapStateToProps(state) {
const { account, priceBook } = state;
const accountId = account.id;
return { accountId, priceBook };
}
NewOrderFormFour = reduxForm({
form: 'createOrder',
destroyOnUnmount: false,
validate,
});
export default connect(mapStateToProps, {
getRevShareAction: getRevShare,
getPriceBookAction: getPriceBook,
})(NewOrderFormFour);
答案 0 :(得分:2)
您刚刚使用reduxForm
创建了装饰器功能,但未将其应用于表单组件。
reduxForm
创建了decorator函数,应该使用react组件作为参数调用,因此你必须使用像这样的somethig:
NewOrderFormFour = reduxForm({
form: 'createOrder',
destroyOnUnmount: false,
validate,
})(NewOrderFormFour);
而不是:
NewOrderFormFour = reduxForm({
form: 'createOrder',
destroyOnUnmount: false,
validate,
});