I have the following react components:
class Statuses extends React.Component {
constructor(props) {
super();
this.groups = props.groups;
this.state = {
statuses: [],
editDialog: null,
deleteDialog: null,
editTransitionsDialog: null,
transitions: null,
editTransitionGroupsDialog: null,
transition: null,
groups: null,
alert: null,
};
}
componentDidMount() {
this.fetchStatuses();
}
handleCloseAlert = () => {
let alertType = this.props.alert.type;
this.props.cleanCatalogsMessage();
if (alertType == ALERT_SUCCESS) {
this.setState({editDialog: null});
}
}
TransitionRight = (props) => {
return <Slide {...props} direction="right" />;
}
fetchStatuses = () => {
this.props.fetchStatuses(this.props.currentForm.id)
}
handleEditClick = (id) => {
if (id) {
this.setState({editDialog: this.props.statuses.find(status => status.id === id)});
} else {
this.setState({editDialog: {id: null, name: null, description: null, color: '#808080'}})
}
}
handleCloseEditDialog = (proceed) => {
if (proceed) {
this.saveStatus();
} else {
this.setState({editDialog: null});
};
}
handleEditStatusChange = (e) => {
let newEditDialog = Object.assign({}, this.state.editDialog)
switch (e.target.name) {
case 'status_name': {
newEditDialog.name = e.target.value;
break;
}
case 'status_description': {
newEditDialog.description = e.target.value;
break;
}
case 'status_slug': {
newEditDialog.slug = e.target.value;
break;
}
case 'status_color': {
newEditDialog.color = e.target.value;
break;
}
default: {
alert("Error on handleEditTransitionChange");
}
}
this.setState({editDialog: newEditDialog});
}
saveStatus = () => {
if (this.state.editDialog.id) {
this.updateStatus();
} else {
this.createStatus();
}
}
updateStatus = () => {
this.props.updateStatus(this.state.editDialog);
}
createStatus = () => {
// const requestString = backendServer + 'api/general/statuses/';
const form = {
automat: this.props.currentForm.id,
name: this.state.editDialog.name,
description: this.state.editDialog.description,
slug: this.state.editDialog.slug,
color: this.state.editDialog.color,
}
this.props.createStatus(form);
}
handleDeleteClick = (id) => {
this.setState({deleteDialog: this.state.statuses.find(status => status.id === id)});
}
handleCloseDeleteDialog = (proceed) => {
if (proceed) {
this.deleteStatus()
} else {
this.setState({deleteDialog: null});
}
}
deleteStatus = () => {
const requestString = backendServer + 'api/general/statuses/' + this.state.deleteDialog.id + '/';
axios.delete(requestString, getAuthHeader())
.then((Response) => {
let newStatusesList = this.state.statuses.slice();
let i = newStatusesList.findIndex((status) => status.id === this.state.deleteDialog.id);
newStatusesList.splice(i, 1);
newStatusesList.forEach((status) => {
let j = status.transitions.findIndex((transition) => transition.id === this.state.deleteDialog.id)
if (j > -1) {
status.transitions.splice(j, 1);
}
})
this.setState({statuses: newStatusesList, deleteDialog: null, alert: "¡El estado se ha eliminado con Éxito!"});
})
.catch((Error) => {
const message = getErrorMessage(Error).message.message;
alert(message)
})
}
handleEditTransitionsClick = (id) => {
let statusToEdit = this.props.statuses.find(status => status.id === id)
this.setState({
editTransitionsDialog: statusToEdit,
transitions: statusToEdit.transitions.slice()
});
}
handleEditTransitionsChange = (e) => {
let newTransitionsList = null;
if (e.target.checked) {
newTransitionsList = this.state.transitions.slice();
let statusToAdd = this.props.statuses.find((status) => status.name === e.target.name);
let transitionToAdd = {id: statusToAdd.id, name: statusToAdd.name};
newTransitionsList.push(transitionToAdd);
} else {
newTransitionsList = this.props.transitions.slice();
let i = newTransitionsList.findIndex((transition) => transition.name === e.target.name);
if (i > -1) {
newTransitionsList.splice(i, 1);
}
}
this.setState({transitions: newTransitionsList});
}
handleCloseEditTransitionsDialog = (proceed) => {
if (proceed) {
this.updateTransitions();
} else {
this.setState({editTransitionsDialog: null, transitions: null});
}
}
updateTransitions = () => {
let transitionsToAdd = this.state.transitions.filter(transition => !this.state.editTransitionsDialog.transitions.includes(transition));
let transitionsToDelete = this.state.editTransitionsDialog.transitions.filter(transition => !this.state.transitions.includes(transition));
const requestString = backendServer + 'api/general/set_transitions/' + this.state.editTransitionsDialog.id + '/';
axios.post(requestString, {add: transitionsToAdd, delete: transitionsToDelete}, getAuthHeader())
.then((Response) => {
let newEditTransitionsDialog = Object.assign({}, this.state.editTransitionsDialog);
transitionsToAdd.forEach((transition) => {
newEditTransitionsDialog.transitions.push({id: transition.id, name: transition.name, groups: []});
})
transitionsToDelete.forEach((transition) => {
let i = newEditTransitionsDialog.transitions.findIndex((transitionToEval) => transitionToEval.id === transition.id);
newEditTransitionsDialog.transitions.splice(i, 1);
})
let newStatusesList = this.state.statuses.slice();
let j = newStatusesList.findIndex((status) => status.id === this.state.editTransitionsDialog.id);
newStatusesList[j] = newEditTransitionsDialog;
this.setState({statuses: newStatusesList, editTransitionsDialog: null, transitions: null, alert: "¡Las transiciones se modificaron con éxito!"});
})
.catch((Error) => {
const message = getErrorMessage(Error).message.message;
alert(message)
})
}
handleEditTransitionGroupsClick = (id, transition) => {
let newEditTransitionGroupsDialog = Object.assign({}, this.props.statuses.find((status) => status.id === id));
let newTransition = Object.assign({}, newEditTransitionGroupsDialog.transitions.find((transitionToEval) => transitionToEval.id === transition));
this.setState({editTransitionGroupsDialog: newEditTransitionGroupsDialog, transition: newTransition, groups: newTransition.groups.slice()});
}
handleEditTransitionGroupsChange = (e) => {
let newGroupsList = this.state.groups.slice();
if (e.target.checked) {
let newGroup = this.groups.find((group) => group.name === e.target.name);
newGroupsList.push({id: newGroup.id, name: newGroup.name});
} else {
let i = this.groups.findIndex((group) => group.name === e.target.name);
newGroupsList.splice(i, 1);
}
this.setState({groups: newGroupsList});
}
handleCloseEditTransitionGroupsDialog = (proceed) => {
if (proceed) {
this.updateGroups();
} else {
this.setState({editTransitionGroupsDialog: null, transition: null, groups: null});
}
}
updateGroups = () => {
let groupsToAdd = this.state.groups.filter(group => !this.state.transition.groups.includes(group));
let groupsToDelete =this.state.transition.groups.filter(group => !this.state.groups.includes(group));
const requestString = backendServer + 'api/general/set_groups/' + this.state.transition.id + '/';
axios.post(requestString, {automat: this.props.currentForm.id, init_status: this.state.editTransitionGroupsDialog.id, final_status: this.state.transition.id, add: groupsToAdd, delete: groupsToDelete}, getAuthHeader())
.then((Response) => {
let newTransition = Object.assign({}, this.state.transition);
newTransition.groups = this.state.groups.slice()
let newStatus = Object.assign({}, this.state.editTransitionGroupsDialog);
let i = newStatus.transitions.findIndex(transition => transition.id === newTransition.id);
newStatus.transitions[i] = newTransition;
let newStatusesList = this.state.statuses.slice();
i = newStatusesList.findIndex(status => status.id === newStatus.id);
newStatusesList[i] = newStatus;
this.setState({
statuses: newStatusesList,
editTransitionGroupsDialog: null,
transition: null,
groups: null,
alert: "¡Los Grupos se modificaron con éxito!"
});
})
.catch((Error) => {
const message = getErrorMessage(Error).message.message;
alert(message);
})
}
StatusSnackbars = () => {
const classes = useStylesStatusSnackBars();
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(true);
};
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
return (
<div className={classes.root}>
<Button variant="outlined" onClick={handleClick}>
Open success snackbar
</Button>
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity="success">
This is a success message!
</Alert>
</Snackbar>
<Alert severity="error">This is an error message!</Alert>
<Alert severity="warning">This is a warning message!</Alert>
<Alert severity="info">This is an information message!</Alert>
<Alert severity="success">This is a success message!</Alert>
</div>
);
}
StatusesTable = () => {
return (
<div width="100%">
{this.props.statuses && this.props.statuses.map((status) => {
return (<this.StatusEntry status={status} />)
})}
</div>
)
}
StatusEntry = (status) => {
const classes = useStylesStatusEntry();
return (
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1bh-content"
id="panel1bh-header"
>
<div style={{paddingRight: "5px"}}>
<Chip label={"[" + status.status.id + "]" + status.status.name} size="large" style={{color: "white", backgroundColor: status.status.color}}/>
</div>
<Typography className={classes.secondaryHeading}>{status.status.description}</Typography>
</AccordionSummary>
<AccordionDetails>
<div>
<div style={{display: "flex", alignContent: "center", paddingBottom: "10px"}}>
<div style={{paddingRight: "5px"}}><Button color="primary" variant="contained" size="small" onClick={() => this.handleEditClick(status.status.id)}>Editar</Button></div>
<div style={{paddingRight: "5px"}}><Button color="primary" variant="contained" size="small" onClick={() => this.handleEditTransitionsClick(status.status.id)}>Transiciones</Button></div>
<div style={{paddingRight: "5px"}}><Button color="secondary" variant="contained" size="small" onClick={() => this.handleDeleteClick(status.status.id)}>Eliminar</Button></div>
</div>
<div>
{status.status.transitions.map((transition) => (
<div>
<IconButton aria-label="groups" size="small" color="primary" onClick={() => this.handleEditTransitionGroupsClick(status.status.id, transition.id)}>
<EditIcon fontSize="inherit" />
</IconButton>
<Chip label={transition.name} size="small" style={{color: transition.color}} variant="outlined"/>
{transition.groups.map((group => (
<Chip label={group.name} size="small" variant="outlined" />
)))}
<div fullwidth style={{paddingBottom: "5px", paddingTop: "5px"}}><Divider /></div>
</div>
))}
</div>
</div>
</AccordionDetails>
</Accordion>
)
}
render() {
const getFinalStatusChip = () => {
let finalStatus = this.props.statuses.find((status) => status.id === this.state.transition.id)
return (
<Chip label={finalStatus.name} size="large" style={{color: "white", backgroundColor: finalStatus.color}} />
)
}
return (
<div>
<div style={{display: 'flex', paddingBottom: '5px'}}>
<div className="col-10" style={{paddingTop: '5px'}}>
<h1>Estados: {this.props.currentForm.name}</h1>
</div>
<div className="col-2">
<Fab size="small" color="primary" aria-label="add" onClick={() => this.handleEditClick(null)}>
<AddIcon />
</Fab>
</div>
</div>
<this.StatusesTable/>
{this.state.editDialog && !this.props.showMessage &&
<Dialog open={this.state.editDialog !== null} aria-labelledby="edit-dialog-title">
{this.state.editDialog.id?
<DialogTitle id="edit-dialog-title">Editar Estado: [{this.state.editDialog.id}]{this.state.editDialog.name}</DialogTitle>:
<DialogTitle id="edit-dialog-title">Crear Estado: [ ] Nuevo Estado</DialogTitle>
}
<DialogContent>
<TextField
autoFocus
margin="dense"
id="status-name"
name="status_name"
label="Nombre"
fullWidth
value={this.state.editDialog.name}
onChange={this.handleEditStatusChange}
/>
<TextField
margin="dense"
id="status-description"
name="status_description"
label="Descripción"
fullWidth
multiline={true}
rows={4}
value={this.state.editDialog.description}
onChange={this.handleEditStatusChange}
/>
<TextField
margin="dense"
id="status_slug"
name="status_slug"
label="Slug"
fullWidth
multiline={false}
value={this.state.editDialog.slug}
onChange={this.handleEditStatusChange}
/>
<div style={{paddingTop: "10px"}}>
<p style={{color: "gray"}}>Color</p>
<CompactPicker
color={this.state.editDialog.color}
onChangeComplete={(color) => this.handleEditStatusChange({target: {name: "status_color", value: color.hex}})
}
/>
</div>
</DialogContent>
<DialogActions>
<Button onClick={() => this.handleCloseEditDialog(false)} color="primary">
Cancelar
</Button>
<Button onClick={() => this.handleCloseEditDialog(true)} color="primary">
Aceptar
</Button>
</DialogActions>
</Dialog>
}
{this.state.deleteDialog &&
<Dialog open={this.state.deleteDialog !== null} aria-labelledby="delete-dialog-title">
<DialogTitle id="delete-dialog-title">Eliminar Estado: [{this.state.deleteDialog.id}]{this.state.deleteDialog.name}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
<div style={{color: 'black'}}>
<span>¿Está seguro de que desea eliminar este Estado:?</span>
<div>
<Chip label={this.state.deleteDialog.name} size="large" style={{color: "white", backgroundColor: this.state.deleteDialog.color}} />
</div>
</div>
</DialogContentText>
</DialogContent>
<DialogActions>
<Button color="primary" onClick={() => this.handleCloseDeleteDialog(true)}>
Aceptar
</Button>
<Button color="primary" autoFocus onClick={() => this.handleCloseDeleteDialog(false)}>
Cancelar
</Button>
</DialogActions>
</Dialog>
}
{this.state.editTransitionsDialog &&
<Dialog aria-labelledby="edit-transitions-dialog-title" open={this.state.editTransitionsDialog !== null}>
<DialogTitle id="edit-transitions-dialog-title">Editar Transiciones</DialogTitle>
<DialogContent dividers>
<div>
<Chip label={this.state.editTransitionsDialog.name} size="large" style={{color: "white", backgroundColor: this.state.editTransitionsDialog.color}} />
</div>
<div>
<FormGroup row>
{this.props.statuses.map((status) => {
return (
<FormControlLabel
control={
<Checkbox
checked={this.state.transitions.find((transition) => transition.id === status.id)}
name={status.name}
color="primary"
onChange={this.handleEditTransitionsChange}
/>
}
label={status.name}
/>
)
})}
</FormGroup>
</div>
</DialogContent>
<DialogActions>
<Button color="primary" onClick={() => this.handleCloseEditTransitionsDialog(true)}>
Aceptar
</Button>
<Button color="primary" autoFocus onClick={() => this.handleCloseEditTransitionsDialog(false)}>
Cancelar
</Button>
</DialogActions>
</Dialog>
}
{this.state.editTransitionGroupsDialog &&
<Dialog aria-labelledby="edit-transition-groups-dialog-title" open={this.state.editTransitionGroupsDialog !== null}>
<DialogTitle id="edit-transition-groups-dialog-title">Editar Grupos</DialogTitle>
<DialogContent dividers>
<div>
<Chip label={this.state.editTransitionGroupsDialog.name} size="large" style={{color: "white", backgroundColor: this.state.editTransitionGroupsDialog.color}} />
<ArrowRightIcon />
{getFinalStatusChip()}
</div>
<div>
<FormGroup row>
{this.groups.map((group) => {
return (
<FormControlLabel
control={
<Checkbox
name={group.name}
color="primary"
checked={this.state.groups.find((groupToEval) => groupToEval.id === group.id)}
onChange={this.handleEditTransitionGroupsChange}
/>
}
label={group.name}
/>
)
})}
</FormGroup>
</div>
</DialogContent>
<DialogActions>
<Button color="primary" onClick={() => this.handleCloseEditTransitionGroupsDialog(true)}>
Aceptar
</Button>
<Button color="primary" autoFocus onClick={() => this.handleCloseEditTransitionGroupsDialog(false)}>
Cancelar
</Button>
</DialogActions>
</Dialog>
}
{this.props.showMessage &&
<AttrAlert
displayCase="alert_dialog"
type={this.props.alert.type}
message={this.props.alert.message}
handleCloseAlert={this.handleCloseAlert}
/>}
</div>
)
}
}
const mapStateToProps = ({auth, catalogs}) => {
const {groups} = auth;
const {statuses, alert, showMessage} = catalogs;
return {groups, statuses, alert, showMessage};
}
const mapDispatchToProps = {
fetchStatuses,
createStatus,
updateStatus,
cleanCatalogsMessage
}
export default connect(mapStateToProps, mapDispatchToProps)(Statuses);
The error raises in component StatusesTable
. I have debugged and found that this.StatusEntry
is never executed because I set a breakpoint inside it, and it is never reached, even when a breackpoint in the line where it is called is always reached.
So StatusEntry
is never executed, but the error raises at the end of the map loop. This is the error I get in the inspector console:
Error: Minified React error #130; visit https://reactjs.org/docs/error-decoder.html?invariant=130&args[]=undefined&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
And this is the full message of the error:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
This error only raises when running the application from minified code in a server with node version 10.19.0, but it runs perfectly when running locally from regular JS code and node version 14.15.3.