reactjs - 反应超过最大更新深度 - 无法发现循环
问题描述
我是一个非常新的开发人员,正在处理遗留代码,所以我现在道歉。但是当我尝试向规则数组“添加规则”时出现此错误。我确实尝试过使用谷歌搜索,并且知道我可能正在循环一个 setState,但我无法发现它。下面是我认为相关的代码,我没有使用 useEffect,我认为我没有直接调用该函数。任何帮助将不胜感激,谢谢。
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import {
Grid,
MenuItem,
TextField,
Typography,
IconButton,
InputAdornment,
Select,
} from '@material-ui/core';
import ArrowIcon from '@material-ui/icons/ArrowDropDown'
import AddIcon from '@material-ui/icons/Add';
import DeleteIcon from '@material-ui/icons/Clear';
import DragHandle from '@material-ui/icons/DragHandle';
import {
DropTarget,
DragSource,
} from 'react-dnd';
import registry from "../registry";
import {Can} from '../utils';
let schema = registry.actions.RUN.configDef;
const styles = theme => ({
row: {
'&:nth-of-type(even)': {
backgroundColor: theme.palette.background.default,
},
borderTop: '1px solid',
borderColor: theme.palette.grey[400]
},
header: {
fontWeight: 'normal',
textAlign: 'center'
},
border: {
border: '1px solid',
borderColor: theme.palette.grey[400],
borderRadius: '4px',
padding: '8px',
overflow: 'hidden'
},
add: {
color: theme.palette.primary.main
},
clear: {
color: theme.palette.error.main
},
textField: {
margin: '8px'
},
rightJustifiedText: {
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
margin: '8px'
},
container: {
width: '100%'
},
iconContainer: {
padding: '0px 8px 0px 8px'
},
flexContainer: {
display: 'flex',
alignItems: 'center'
}
});
function CustomArrowIcon(props) {
return (<ArrowIcon {...props} style={{position: 'absolute', left: 0}}/>)
}
function RowContent({id, onRemove, children, connectDropTarget, connectDragSource, isDragging, classes, ability, accountNumber}) {
const opacity = isDragging ? 0.5 : 1;
return (<Grid className={classes.row} item xs={12} key={id}>
{connectDropTarget(
<span className={classes.flexContainer} style={{opacity}}>
{connectDragSource(
<span className={classes.iconContainer}>
<Can I={'update'} a={'serviceActionProfile'} where={'accountNumber'} equals={accountNumber} ability={ability}>
{can => (
<IconButton disabled={!can}>
<DragHandle/>
</IconButton>
)}
</Can>
</span>
)}
{children}
<span className={classes.iconContainer}>
<Can I={'update'} a={'serviceActionProfile'} where={'accountNumber'} equals={accountNumber} ability={ability}>
{can => (
<IconButton disabled={!can} onClick={onRemove}>
<DeleteIcon className={can ? classes.clear : undefined}/>
</IconButton>
)}
</Can>
</span>
</span>
)}
</Grid>);
}
const DraggableRow = DropTarget('row', {
hover: (props, monitor) => {
const monitorItem = monitor.getItem();
const dragIndex = monitorItem.index;
const hoverIndex = props.index;
if (dragIndex === hoverIndex) return null;
let offsetY = monitor.getClientOffset().y;
// When a short item is swapped with a tall item the short item will continue to hover the tall item
// This would normally cause the two to be swapped again, and again, and again...
// Dont re-swap the items until the mouse moves in the other direction
if (dragIndex < hoverIndex && offsetY <= monitorItem.offsetY) {
monitorItem.offsetY = offsetY;
return
}
if (dragIndex > hoverIndex && offsetY >= monitorItem.offsetY) {
monitorItem.offsetY = offsetY;
return
}
props.moveRow(dragIndex, hoverIndex);
monitorItem.index = hoverIndex;
monitorItem.offsetY = offsetY;
},
}, (connect) => {
return {
connectDropTarget: connect.dropTarget()
};
})(DragSource('row', {
beginDrag: (props, monitor) => {
return { index: props.index, offsetY: monitor.getClientOffset().y };
},
}, (connect, monitor) => {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
};
})(withStyles(styles)(RowContent)));
class Modify extends React.Component {
constructor(props) {
super(props);
let now = Date.now();
this.state = {
rules: (props.configValues.rules || []).slice().sort((a,b) => a.index-b.index).map((rule, index) => {
return {
...rule,
toIsValid: validateToFrom('', rule.to.path.trim(), props.serviceActionProfile.jsonataVersion),
fromIsValid: validateToFrom(rule.from.type, rule.from.value.trim(), props.serviceActionProfile.jsonataVersion),
id: ++now,
index
};
})
};
}
componentDidMount() {
this._notifyParent();
}
onChangeAction = (index, event) => {
let action = event.target.value;
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules[index].action = action;
return {
rules: newRules
};
}, this._notifyParent);
};
onChangeToPrefix = (index, event) => {
let prefix = event.target.value;
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules[index].to.prefix = prefix;
return {
rules: newRules
};
}, this._notifyParent);
};
onChangeToPath = (index, event) => {
let path = event.target.value;
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules[index].to.path = path;
newRules[index].toIsValid = validateToFrom('', path.trim(), this.props.serviceActionProfile.jsonataVersion);
return {
rules: newRules
};
}, this._notifyParent);
};
onChangeFromType = (index, event) => {
let type = event.target.value;
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules[index].from.type = type;
newRules[index].fromIsValid = validateToFrom(type, newRules[index].from.value.trim(), this.props.serviceActionProfile.jsonataVersion);
return {
rules: newRules
};
}, this._notifyParent);
};
onChangeFromValue = (index, event) => {
let value = event.target.value;
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules[index].from.value = value;
newRules[index].fromIsValid = validateToFrom(newRules[index].from.type, value.trim(), this.props.serviceActionProfile.jsonataVersion);
return {
rules: newRules
};
}, this._notifyParent);
};
onRemoveRule = (index) => {
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules.splice(index, 1);
newRules.forEach((map, index) => {
map.index = index;
});
return {
rules: newRules
}
}, this._notifyParent);
};
addRule = () => {
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules.push({
id: Date.now(),
index: newRules.length,
action: 'Set',
to: {
prefix: 'local.',
path: ''
},
from: {
type: 'local.',
value: ''
}
});
return {
rules: newRules
};
}, this._notifyParent);
};
_notifyParent() {
let config = {
rules: this.state.rules.map(rule => {
let clone = {
...rule
};
delete clone.id;
delete clone.toIsValid;
delete clone.fromIsValid;
return clone;
})
};
const profile = {
...this.props.serviceActionProfile, // Add any additional properties that might be on the profile and used for validation like the jsonataVersion.
name: "PLACEHOLDER",
action: 'RUN',
documents: {},
configValues: config, // rules
};
const validation = validate(profile);
this.props.onChange({}, config, validation.validOverall);
}
moveRow = (from, to) => {
this.setState(oldState => {
let newRules = oldState.rules.slice();
let moving = newRules.splice(from, 1)[0];
newRules.splice(to, 0, moving);
newRules.forEach((map, index) => {
map.index = index;
});
return {
rules: newRules
}
}, this._notifyParent);
};
render() {
const { classes, ability, accountNumber } = this.props;
const textFieldMargin = 'dense';
return (
<div className={classes.border}>
<Can I={'update'} a={'serviceActionProfile'} where={'accountNumber'} equals={accountNumber} ability={ability}>
{can => (
<React.Fragment>
<Grid container
spacing={16}
direction={"column"}>
<Grid item xs={12} container alignItems={'center'}>
<Grid item xs={1}>
<span className={classes.iconContainer}>
<IconButton className={classes.add} disabled={!can} onClick={this.addRule}>
<AddIcon/>
</IconButton>
</span>
</Grid>
<Grid item xs={10}>
<Typography component={'div'} className={classes.header}>
Rules
</Typography>
</Grid>
</Grid>
{this.state.rules.slice().sort((a,b) => a.index-b.index).map(({action, to, toIsValid, from, fromIsValid, id}, index) => {
return (<DraggableRow key={id}
index={index}
onRemove={this.onRemoveRule.bind(this, index)}
moveRow={can ? this.moveRow : ()=>{}}
ability={ability}
accountNumber={accountNumber}>
<div className={classes.container}>
<span className={classes.flexContainer}>
<TextField select
className={classes.textField}
variant={'outlined'}
margin={textFieldMargin}
style={{width: '150px'}}
value={action}
fullWidth
disabled={!can}
onChange={this.onChangeAction.bind(this, index)}>
{schema.properties.rules.items.properties.action.enum.map(value => {
return (<MenuItem key={value} value={value}>{value}</MenuItem>)
})}
</TextField>
<TextField variant={'outlined'}
label={'Field'}
className={classes.textField}
margin={textFieldMargin}
value={to.path}
fullWidth
disabled={!can}
error={!toIsValid}
onChange={this.onChangeToPath.bind(this, index)}
inputProps={{style: {paddingLeft: '4px'}}}
InputProps={{
style: {width: 'calc(100% - 14px)'},
startAdornment: (
<InputAdornment>
<Select value={to.prefix}
disabled={!can}
onChange={this.onChangeToPrefix.bind(this, index)}
IconComponent={CustomArrowIcon}
SelectDisplayProps={{
style: {paddingLeft: '24px', paddingRight: '0px'}
}}>
{schema.properties.rules.items.properties.to.properties.prefix.enum.map(value => {
return (<MenuItem key={value} value={value}>{value}</MenuItem>)
})}
</Select>
</InputAdornment>
)
}}/>
</span>
{action === 'Set' ? (
<span className={classes.flexContainer}>
<Typography className={classes.rightJustifiedText} component='span' style={{width: '150px'}}>to</Typography>
<TextField variant={'outlined'}
label={'Value'}
className={classes.textField}
margin={textFieldMargin}
value={from.value}
fullWidth
disabled={!can}
error={!fromIsValid}
onChange={this.onChangeFromValue.bind(this, index)}
inputProps={{style: {paddingLeft: '4px'}}}
InputProps={{
style: {width: 'calc(100% - 14px)'},
startAdornment: (
<InputAdornment>
<Select value={from.type}
onChange={this.onChangeFromType.bind(this, index)}
IconComponent={CustomArrowIcon}
disabled={!can}
SelectDisplayProps={{
style: {paddingLeft: '24px', paddingRight: '0px'}
}}>
{schema.properties.rules.items.properties.from.properties.type.enum.map(value => {
return (<MenuItem key={value} value={value}>{value}</MenuItem>)
})}
</Select>
</InputAdornment>
)
}}/>
</span>
) : null}
</div>
</DraggableRow>)
})}
</Grid>
</React.Fragment>
)}
</Can>
</div>
);
}
}
Modify.defaultProps = {
requiredDocuments: {},
documents: {},
configValues: {
rules: []
},
serviceActionProfile: {}
};
Modify.propTypes = {
requiredDocuments: PropTypes.object,
documents: PropTypes.object,
configValues: PropTypes.object,
onChange: PropTypes.func.isRequired,
accountNumber: PropTypes.number.isRequired,
ability: PropTypes.object.isRequired,
serviceActionProfile: PropTypes.object
};
export default withStyles(styles)(Modify);
解决方案
推荐阅读
- c# - Xamarin Forms iOS CustomRenderer Entry PlaceHolder 和 Button Text Padding Bottom
- .net - 使用 Jenkins 构建时,Xamarin.Forms 项目中缺少 netstandard 2.0 参考
- python - 如何将输入传递给 Keras 中的 2D Conv?
- powershell - 将参数添加到 SQLCommand 并稍后添加值
- c - 我想检查用户是否输入了一些东西
- angular - 通过共享的 Angular Material 模块简化应用程序组件中的导入
- python - Python 代码分步运行而不是整体运行
- android - 如何通过 Kotlin 将拖动视图的 ID 传递给 setImageResource(resID:Int)?
- android - 在 Android 上使用 Expo 设置排毒
- c# - 如何从 GPS Tracker Mini GT06 读取登录信息