availity-reactstrap-validation
availity-reactstrap-validation copied to clipboard
Uncaught Error: Maximum update depth exceeded
Hello!
When I'm try using multiple AvField
in the same AvForm
, I'm get an error:
invariant.js:42 Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
at invariant (invariant.js:42)
at scheduleWorkImpl (react-dom.development.js:12098)
at scheduleWork (react-dom.development.js:12055)
at Object.enqueueSetState (react-dom.development.js:6632)
at ProxyComponent.Component.setState (react.development.js:242)
at ProxyComponent.setError (AvForm.js:375)
at ProxyComponent.wrappedMethod (react-hot-loader.development.js:506)
at ProxyComponent.unregisterInput (AvForm.js:219)
at ProxyComponent.wrappedMethod (react-hot-loader.development.js:506)
at ProxyComponent.componentWillUnmount (AvBaseInput.js:109)
This is my code:
-
IncomeOperationItem.js
- this is a parent for componentsPursesSelector
,OperationAmountInput
andIncomeItemsSelector
(where I'm using Availity components):
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'
import { Row, Col, Form, FormGroup, Label, Input, FormText, Button, Card, CardBody } from 'reactstrap'
import { AvForm, AvField, AvGroup, AvInput, AvFeedback, AvRadioGroup, AvRadio } from 'availity-reactstrap-validation';
import AddCopyOperationButton from 'components/Operations/OperationActions/AddCopyOperationButton';
import RemoveOperationButton from 'components/Operations/OperationActions/RemoveOperationButton';
import RemoveSelectedOperation from 'components/Operations/NewOperations/RemoveSelectedOperation';
import { IncomeOperationPropTypes } from 'constants/propTypes/operations';
import {
changeIncomeOperationAmount, changeIncomeOperationPurse, changeIncomeOperationIncomeItem, changeIncomeOperationTags
} from 'actions/operations';
import {
createNewTag
} from 'actions/handbooks';
import { PursePropTypes, IncomeItemPropTypes, TagPropTypes } from 'constants/propTypes/handbooks';
import TagsAutocompleteInput from 'components/Operations/NewOperations/base/TagsAutocompleteInput';
import * as arrayUtils from 'utils/arrayUtils';
import IncomeItemsSelector from 'components/Operations/NewOperations/base/IncomeItemsSelector';
import PursesSelector from 'components/Operations/NewOperations/base/PursesSelector';
import OperationAmountInput from 'components/Operations/NewOperations/base/OperationAmountInput';
import OperationTagsInput from 'components/Operations/NewOperations/base/OperationTagsInput';
import { findNewTags } from 'utils/tagsUtils';
class IncomeOperationItem extends Component {
constructor(props) {
super(props);
}
addCopyOfBlock = () => {
const { incomeOperation, addCopyOfBlock } = this.props
addCopyOfBlock(incomeOperation.number)
}
removeBlock = (number) => {
const { removeBlock } = this.props
removeBlock(number)
}
handleChangeAmount = (newAmount) => {
const { changeIncomeOperationAmount, incomeOperation } = this.props
changeIncomeOperationAmount(incomeOperation.number, newAmount)
}
handleChangeSelectedPurse = (selectedPurse) => {
const { changeIncomeOperationPurse, incomeOperation } = this.props
changeIncomeOperationPurse(incomeOperation.number, selectedPurse)
}
handleChangeSelectedIncomeItem = (selectedIncomeItem) => {
const { changeIncomeOperationIncomeItem, incomeOperation } = this.props
changeIncomeOperationIncomeItem(incomeOperation.number, selectedIncomeItem)
}
handleChangeSelectedTags = (selectedTags) => {
const { tags, changeIncomeOperationTags, incomeOperation } = this.props
changeIncomeOperationTags(incomeOperation.number, selectedTags)
if (selectedTags) {
findNewTags(selectedTags, tags, this.handleCreateNewTag)
}
}
handleCreateNewTag = (newTags) => {
console.log('New tags: ', newTags)
const { createNewTag } = this.props
if (newTags) {
createNewTag(newTags)
}
}
render() {
const { incomeOperation, isRemoveButtonEnabled, tags, incomeItems, purses } = this.props
const removeButton = isRemoveButtonEnabled
? <RemoveSelectedOperation number={incomeOperation.number} onRemove={this.removeBlock} />
: null
const elementIdSuffix = incomeOperation.number
const purseSelectId = `purseSelect${elementIdSuffix}`
const plusInputId = `plus${elementIdSuffix}`
const incomeItemSelectId = `incomeItemSelect${elementIdSuffix}`
const tagsInputId = `tags${elementIdSuffix}`
const isHighlightedForRemoveClass = incomeOperation.isHighlightedForRemove
? 'operation-details-item--for-remove'
: ''
return (
<li className={`list-group-item list-group-item-light operation-details-item ${isHighlightedForRemoveClass}`}>
<AvForm>
<AvGroup row>
<Col sm={5}>
<PursesSelector id={purseSelectId}
selectedPurse={incomeOperation.purse}
purses={purses}
onChange={this.handleChangeSelectedPurse} />
</Col>
<Col sm={6}>
<OperationAmountInput id={plusInputId}
selectedValue={incomeOperation.plus}
onChange={this.handleChangeAmount}
/>
</Col>
<Col sm={1} className='text-right'>
<AddCopyOperationButton number={incomeOperation.number} onClick={this.addCopyOfBlock} />
</Col>
</AvGroup>
<FormGroup row>
<Col sm={5}>
<IncomeItemsSelector id={incomeItemSelectId}
selectedIncomeItem={incomeOperation.incomeItem}
incomeItems={incomeItems}
onChange={this.handleChangeSelectedIncomeItem}
/>
</Col>
<Col sm={6}>
<OperationTagsInput id={tagsInputId}
selectedTags={incomeOperation.tags}
tags={tags}
onChange={this.handleChangeSelectedTags}
onCreateNewTag={this.handleCreateNewTag}
/>
</Col>
<Col sm={1} className='text-right'>
{removeButton}
</Col>
</FormGroup>
</AvForm>
</li>
);
}
}
IncomeOperationItem.propTypes = {
incomeOperation: IncomeOperationPropTypes,
isRemoveButtonEnabled: PropTypes.bool,
tags: PropTypes.arrayOf(TagPropTypes),
purses: PropTypes.arrayOf(PursePropTypes),
incomeItems: PropTypes.arrayOf(IncomeItemPropTypes),
addCopyOfBlock: PropTypes.func,
removeBlock: PropTypes.func,
};
export default connect(
null,
{
changeIncomeOperationAmount, changeIncomeOperationPurse, changeIncomeOperationIncomeItem, changeIncomeOperationTags,
createNewTag
}
)(IncomeOperationItem);
-
PursesSelector.js
:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Label, Input } from 'reactstrap'
import { AvForm, AvField, AvGroup, AvInput, AvFeedback, AvRadioGroup, AvRadio } from 'availity-reactstrap-validation';
import { AvValidator } from 'availity-reactstrap-validation'
import { PursePropTypes } from 'constants/propTypes/handbooks';
import { IdPropTypes, IdPropTypesAsNotRequired } from 'constants/propTypes/base';
class PursesSelector extends Component {
constructor(props) {
super(props)
}
handleChangeSelectedPurse = (e) => {
const { onChange } = this.props
if (onChange) {
onChange(e.target.value)
}
}
render() {
const { id, selectedPurse } = this.props
const title = 'Выберите кошелек'
return (
<div>
<Label for={id} hidden>{title}</Label>
<AvField type='select' name='select' id={id}
required errorMessage='Укажите кошелек'
value={selectedPurse}
onChange={this.handleChangeSelectedPurse}
title={title}>
<option value=''>- Кошелек -</option>
{this.renderPurses()}
</AvField>
{/* <Input type='select' name='select' id={id}
value={selectedPurse}
onChange={this.handleChangeSelectedPurse}
title={title}>
<option value=''>- Кошелек -</option>
{this.renderPurses()}
</Input> */}
</div>
);
}
renderPurses = () => {
const { purses } = this.props
const items = purses.map(purse => {
return <option key={purse.id} value={purse.id}>{purse.uiName}</option>
})
return items
}
}
PursesSelector.propTypes = {
id: PropTypes.string,
selectedPurse: IdPropTypesAsNotRequired,
purses: PropTypes.arrayOf(PursePropTypes),
onChange: PropTypes.func,
};
export default PursesSelector;
-
OperationAmountInput.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Label, Input } from 'reactstrap'
import { AvForm, AvField, AvGroup, AvInput, AvFeedback, AvRadioGroup, AvRadio } from 'availity-reactstrap-validation';
import { AvValidator } from 'availity-reactstrap-validation'
import { max20DigitsWithPrecision2Regex } from 'constants/regex';
const operationAmountValueErrorMessage = 'Проверьте, корректно ли указана сумма дохода - правильно: "1200", "500.15", "430,20" (после запятой/точки - максимум 2 символа)'
const operationAmountRequiredErrorMessage = 'Укажите сумму операции'
class OperationAmountInput extends Component {
constructor(props) {
super(props);
this.state = {
operationAmountErrorMessage: '',
}
}
validateOperationAmount = (value, context, input, callback) => {
if (!AvValidator.required(value, context)) {
this.setState({ operationAmountErrorMessage: operationAmountRequiredErrorMessage })
callback(false)
} else
if (!AvValidator.pattern(value, context, { value: input.props.pattern })) {
this.setState({ operationAmountErrorMessage: operationAmountValueErrorMessage })
callback(false)
} else {
callback(true)
const currentErrorMessage = this.state.operationAmountErrorMessage
if (currentErrorMessage && currentErrorMessage.length > 0) {
this.setState({ operationAmountErrorMessage: '' })
}
}
}
handleChangeOperationAmount = (e) => {
const { onChange } = this.props
if (onChange)
onChange(e.target.value)
}
render() {
const { id, selectedValue } = this.props
const title = `${operationAmountRequiredErrorMessage}. После ввода - ${operationAmountValueErrorMessage.toLowerCase()}`
return (
<div>
<Label for={id} hidden>Укажите сумму операции</Label>
{/* <Input type='text' name='text' id={id} placeholder='Сумма операции'
required errorMessage='Укажите сумму операции'
value={selectedValue}
onChange={this.handleChangeOperationAmount}
/> */}
<AvField name='pattern' id={id} type='text' placeholder='Сумма операции'
required pattern={max20DigitsWithPrecision2Regex}
errorMessage={this.state.operationAmountErrorMessage}
validate={{ custom: this.validateOperationAmount }}
title={title} />
</div>
);
}
}
OperationAmountInput.propTypes = {
id: PropTypes.string,
selectedValue: PropTypes.string,
onChange: PropTypes.func,
};
export default OperationAmountInput;
-
IncomeItemsSelector.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Label, Input } from 'reactstrap'
import { AvForm, AvField, AvGroup, AvInput, AvFeedback, AvRadioGroup, AvRadio } from 'availity-reactstrap-validation';
import { AvValidator } from 'availity-reactstrap-validation'
import OverlayLoader from 'components/base/OverlayLoader';
import { IncomeItemPropTypes } from 'constants/propTypes/handbooks';
import { IdPropTypes, IdPropTypesAsNotRequired } from 'constants/propTypes/base';
const incomeItemRequiredErrorMessage = 'Укажите статью дохода'
class IncomeItemsSelector extends Component {
constructor(props) {
super(props)
this.state = {
selectedIncomeItemErrorMessage: '',
}
}
validateSelectedIncomeItem = (value, context, input, callback) => {
if (!AvValidator.required(value, context)) {
this.setState({ selectedIncomeItemErrorMessage: incomeItemRequiredErrorMessage })
callback(false)
} else {
callback(true)
const currentErrorMessage = this.state.selectedIncomeItemErrorMessage
if (currentErrorMessage && currentErrorMessage.length > 0) {
this.setState({ selectedIncomeItemErrorMessage: '' })
}
}
}
handleChangeSelectedIncomeItem = (e) => {
const { onChange } = this.props
if (onChange) {
onChange(e.target.value)
}
}
render() {
const { id, selectedIncomeItem } = this.props
const title = 'Выберите статью доходов'
return (
<div>
<Label for={id} hidden>{title}</Label>
<AvField type='select' name='select'
id={id}
required errorMessage='Укажите статью дохода'
value={selectedIncomeItem}
onChange={this.handleChangeSelectedIncomeItem}
title={title}>
<option value=''>- Статья доходов -</option>
{this.renderIncomeItems()}
</AvField>
{/* <Input type='select' name='select' id={id}
value={selectedIncomeItem}
onChange={this.handleChangeSelectedIncomeItem}
title={title}>
<option value=''>- Статья доходов -</option>
{this.renderIncomeItems()}
</Input> */}
</div>
);
}
renderIncomeItems = () => {
const { incomeItems } = this.props
const items = incomeItems.map(incomeItem => {
return <option key={incomeItem.id} value={incomeItem.id}>{incomeItem.name}</option>
})
return items
}
}
IncomeItemsSelector.propTypes = {
id: PropTypes.string,
selectedIncomeItem: IdPropTypesAsNotRequired,
incomeItems: PropTypes.arrayOf(IncomeItemPropTypes),
onChange: PropTypes.func,
};
export default IncomeItemsSelector;
If I remove AvField
in one of the 3 components (PursesSelector
, IncomeItemSelector
or OperationAmountInput
) then no errors occured. As soon as I add the 3rd AvField
again, the browser tab starts to hang, and the error appears in the developer toolbar.
My package.json
is:
{
"name": "finance_client_web_spa",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "npm run dev",
"dev": "webpack-dev-server --hot --inline",
"build": "webpack"
},
"author": "Abakumov Valeriy",
"devDependencies": {
"autoprefixer": "^7.1.6",
"babel-core": "^6.13.2",
"babel-loader": "^6.2.4",
"babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.11.1",
"babel-preset-react-hmre": "^1.1.1",
"babel-preset-stage-0": "^6.5.0",
"css-hot-loader": "^1.3.9",
"css-loader": "^0.28.9",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.11",
"html-webpack-plugin": "^3.2.0",
"node-sass": "^4.8.3",
"postcss-loader": "^2.1.3",
"precss": "^3.1.2",
"prop-types": "^15.6.1",
"react-hot-loader": "^4.0.1",
"redux-logger": "^3.0.6",
"sass-loader": "^6.0.7",
"style-loader": "^0.19.1",
"url-loader": "^1.0.1",
"webpack": "^3.0.0",
"webpack-dev-server": "^2.9.1",
"webpack-merge": "^4.1.2"
},
"dependencies": {
"@fortawesome/fontawesome": "^1.1.5",
"@fortawesome/fontawesome-free-brands": "^5.0.10",
"@fortawesome/fontawesome-free-regular": "^5.0.10",
"@fortawesome/fontawesome-free-solid": "^5.0.10",
"@fortawesome/fontawesome-free-webfonts": "^1.0.6",
"availity-reactstrap-validation": "^2.0.2",
"bootstrap": "^4.0.0",
"font-awesome": "^4.7.0",
"history": "^4.7.2",
"immutability-helper": "^2.6.6",
"immutable": "^4.0.0-rc.9",
"jquery": "^3.3.1",
"keymirror": "^0.1.1",
"loaders.css": "^0.1.2",
"popper.js": "^1.14.3",
"prop-types": "^15.6.0",
"react": "^16.0.0",
"react-addons-css-transition-group": "^15.6.2",
"react-autosuggest": "^9.3.4",
"react-bootstrap4-form-validation": "^1.0.5",
"react-css-transition": "^0.7.4",
"react-day-picker": "^7.0.7",
"react-dom": "^16.0.0",
"react-loader-advanced": "^1.7.1",
"react-loaders": "^3.0.1",
"react-overlay-loading": "^1.0.3",
"react-redux": "^5.0.6",
"react-router-dom": "^4.2.2",
"react-router-redux": "^5.0.0-alpha.9",
"react-select": "^1.2.1",
"react-tagsinput": "^3.19.0",
"react-transition-group": "^2.4.0",
"reactstrap": "^5.0.0",
"redux": "^3.7.2",
"redux-thunk": "^2.2.0",
"reselect": "^3.0.1"
}
}
What I do wrong with Av*** components?
There is probably something wrong with the way you are rendering the components themselves. If you want to post a codesandbox to replicate the issue that would help as I am unable to using multiple AvFields
.
Hello! Thank you, I will post code to some sandbox later)
@abakumov-v We have the same issue, did you find a solution?