首页 > 解决方案 > 反应超过最大更新深度 - 无法发现循环

问题描述

我是一个非常新的开发人员,正在处理遗留代码,所以我现在道歉。但是当我尝试向规则数组“添加规则”时出现此错误。我确实尝试过使用谷歌搜索,并且知道我可能正在循环一个 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);

标签: reactjs

解决方案


推荐阅读