Related
I am facing the difficulty that I want to rewrite the component to use react hooks, yet suddenly the DropZone component stopped working. I need to change the Component to hooks due to the JWT authentication I use.
I have this (former) implementation of the component:
import React from "react";
import Dropzone from "react-dropzone";
import UploadFileService from "../../../services/upload-files.service";
import './FileUploadForm.css'
import {withTranslation} from "react-i18next";
import Button from "#mui/material/Button";
import LinearProgressWithLabel from "./LinearProgressWithLabel";
import Typography from "#mui/material/Typography";
import {Alert} from "#mui/material";
import AuthContext from "../../../context/AuthContext";
class FileUploadForm extends React.Component {
constructor(props) {
super(props);
this.upload = this.upload.bind(this);
this.removeFiles = this.removeFiles.bind(this);
this.onDrop = this.onDrop.bind(this);
this.uploadFiles = this.uploadFiles.bind(this);
this.state = {
selectedFiles: undefined,
progressInfos: [],
message: [],
fileInfos: [],
dropZoneVisible: true,
url: props.url,
multipleFiles: props.multipleFiles
};
}
onDrop(files) {
if (files.length > 0) {
this.setState({selectedFiles: files});
}
}
uploadFiles() {
const selectedFiles = this.state.selectedFiles;
let _progressInfos = [];
for (let i = 0; i < selectedFiles.length; i++) {
_progressInfos.push({percentage: 0, fileName: selectedFiles[i].name});
}
this.setState(
{
progressInfos: _progressInfos,
message: [],
},
() => {
for (let i = 0; i < selectedFiles.length; i++) {
this.upload(i, selectedFiles[i]);
}
}
);
}
upload(idx, file) {
let _progressInfos = [...this.state.progressInfos];
UploadFileService.upload(file, this.state.url, (event) => {
_progressInfos[idx].percentage = Math.round(
(100 * event.loaded) / event.total
);
this.setState({
_progressInfos,
});
})
.then((response) => {
this.setState((prev) => {
let nextMessage = [
...prev.message,
"Uploaded the file successfully: " + file.name,
];
return {
message: nextMessage,
dropZoneVisible: false
};
});
return UploadFileService.getFiles(this.state.url);
})
.then((files) => {
this.setState({
fileInfos: files.data,
});
})
.catch(() => {
_progressInfos[idx].percentage = 0;
this.setState((prev) => {
let nextMessage = [
...prev.message,
"Could not upload the file: " + file.name,
];
return {
progressInfos: _progressInfos,
message: nextMessage,
};
});
});
}
render() {
const {t} = this.props;
const {selectedFiles, progressInfos, message, fileInfos} = this.state;
return (
<div style={{marginTop: '1em'}}>
{progressInfos &&
progressInfos.map((progressInfo, index) => (
<div className="mb-2" key={index} style={{
textAlign: "center",
fontWeight: "bold"
}}>
<Typography component="span" variant="string" align="center">
{progressInfo.fileName}
</Typography>
<LinearProgressWithLabel variant="determinate" value={progressInfo.percentage}/>
</div>
))}
<Dropzone onDrop={this.onDrop} multiple={this.state.multipleFiles}>
{({getRootProps, getInputProps}) => (
<section>
<div {...getRootProps({className: "dropzone"})}>
<input {...getInputProps()} />
{selectedFiles &&
Array.isArray(selectedFiles) &&
selectedFiles.length ? (
<div className="selected-file">
{selectedFiles.length > 3
? `${selectedFiles.length} files`
: selectedFiles.map((file) => file.name).join(", ")}
</div>
) : (
t("label", "Drag and drop file here, or click to select file", {ns: "fileUploadForm"})
)}
</div>
<aside className="selected-file-wrapper"
style={{
display: "flex",
justifyContent: "space-evenly"
}}
>
<Button
className="btn btn-success"
disabled={!selectedFiles}
variant="contained"
onClick={this.uploadFiles}
>
{t("button", "Submit", {ns: "fileUploadForm"})}
</Button>
<Button
className="btn"
color="error"
disabled={!selectedFiles}
variant="contained"
onClick={this.removeFiles}
>
{/*{t("button", "Submit", {ns: "fileUploadForm"})}*/}
Beheben
</Button>
</aside>
</section>
)}
</Dropzone>
{message.length > 0 && (
<div style={{
marginTop: "1em"
}}>
{message.map((item, i) => {
return (
<Alert severity={
item.includes("success") ? "success" : "warning"}>
{item}
</Alert>);
})}
</div>
)}
{fileInfos.length > 0 && (
<div className="card">
<div className="card-header">List of Files</div>
<ul className="list-group list-group-flush">
{fileInfos && fileInfos.map((file, index) => (
<li className="list-group-item" key={index}>
<a href={file.url}>{file.name}</a>
</li>
))}
</ul>
</div>
)}
</div>
);
}
}
export default withTranslation()(FileUploadForm);
This component works perfectly, meaning that the selectedFiles get propagated so the users can see what they submit. However, when changed to the functional implementation using hooks:
import React, {useCallback, useEffect, useRef, useState} from "react";
import Dropzone from "react-dropzone";
import './FileUploadForm.css'
import {useTranslation, withTranslation} from "react-i18next";
import Button from "#mui/material/Button";
import LinearProgressWithLabel from "./LinearProgressWithLabel";
import Typography from "#mui/material/Typography";
import {Alert} from "#mui/material";
import useAxios from "../../../utils/useAxios";
function FileUploadForm(url, multipleFiles, handleUploadReload) {
const api = useAxios();
const {t, i18n} = useTranslation()
const {selectedFiles, setSelectedFiles} = useState(undefined);
const {progressInfos, setProgressInfos} = useState({val: []});
const {message, setMessage} = useState([]);
const {targetUrl, setTargetUrl} = useState(url);
const {multipleFilesEnabled, setMultipleFilesEnabled} = useState(multipleFiles);
const {formUploaded, setFormUploaded} = useState(false);
const progressInfosRef = useRef(null)
const onDrop = (files) => {
if (files && files.length > 0) {
setSelectedFiles(files);
setProgressInfos({ val: [] });
}
}
const resetFormState = () => {
setFormUploaded(false);
setSelectedFiles(undefined);
setProgressInfos([]);
setMessage([]);
}
const removeFiles = () => {
setSelectedFiles(undefined);
setProgressInfos([]);
setMessage([]);
}
const uploadFiles = () => {
const files = Array.from(selectedFiles);
let _progressInfos = files.map(file => ({percentage: 0, fileName: file.name}));
progressInfosRef.current = {
val: _progressInfos,
}
files.map((file, i) => upload(i, file));
setMessage([]);
};
const upload = (idx, file) => {
let _progressInfos = [...progressInfosRef.current.val];
let formData = new FormData();
formData.append("file", file)
const onUploadProgress = (event) => {
_progressInfos[idx].percentage = Math.round(
(100 * event.loaded) / event.total
);
setProgressInfos({val: _progressInfos});
}
return api.post(targetUrl, formData, {
headers: {
"Content-Type": "multipart/form-data",
}, onUploadProgress
}).then((response) => {
handleUploadReload(response.data["files"]);
setMessage((prev) => ([
...prev.message,
"Uploaded the file successfully: " + file.name,
]
));
setFormUploaded(true);
})
.catch(() => {
_progressInfos[idx].percentage = 0;
setProgressInfos({val: _progressInfos});
setMessage((prev) => ([
...prev.message,
"Could not upload the file: " + file.name,
]));
});
}
return (
<div style={{marginTop: '1em'}}>
{progressInfos &&
progressInfos.val.length > 0 &&
progressInfos.val.map((progressInfo, index) => (
<div className="mb-2" key={index} style={{
textAlign: "center",
fontWeight: "bold"
}}>
<Typography component="span" variant="string" align="center">
{progressInfo.fileName}
</Typography>
<LinearProgressWithLabel variant="determinate" value={progressInfo.percentage}/>
</div>
))}
<Dropzone onDrop={onDrop} multiple={multipleFilesEnabled}>
{({getRootProps, getInputProps}) => (
<section>
<div {...getRootProps({className: "dropzone"})}>
<input {...getInputProps()} />
{selectedFiles &&
Array.isArray(selectedFiles) &&
selectedFiles.length ? (
<div className="selected-file">
{selectedFiles.length > 3
? `${selectedFiles.length} files`
: selectedFiles.map((file) => file.name).join(", ")}
</div>
) : (
t("label", "Drag and drop file here, or click to select file", {ns: "fileUploadForm"})
)}
</div>
<aside className="selected-file-wrapper"
style={{
display: "flex",
justifyContent: "space-evenly"
}}
>
{!formUploaded ? [
<Button
className="btn btn-success"
disabled={!selectedFiles}
variant="contained"
onClick={uploadFiles}
>
{t("button", "Submit", {ns: "fileUploadForm"})}
</Button>,
<Button
className="btn"
color="error"
disabled={!selectedFiles}
variant="contained"
onClick={removeFiles}
>
{/* TODO: {t("button", "Submit", {ns: "fileUploadForm"})}*/}
Beheben
</Button>] : (
<Button
className="btn"
color="error"
variant="contained"
onClick={resetFormState}
>
{/*TODO: {t("button", "Submit", {ns: "fileUploadForm"})}*/}
Neu Hochladen
</Button>
)}
</aside>
</section>
)}
</Dropzone>
{message && message.length > 0 && (
<div style={{
marginTop: "1em"
}}>
{message.map((item, i) => {
return (
<Alert severity={
item.includes("success") ? "success" : "warning"}>
{item}
</Alert>);
})}
</div>
)}
</div>
);
}
export default withTranslation()(FileUploadForm);
where the utilization of useAxios is essential. Nevertheless, users cannot see what they uploaded (preload view before push request). Any clue what could be wrong?
TLDR
DropZone shows uploaded files in the browser (before post request) when FileUploadForm is class and does not in case of a function.
I have child component and it has a button to dispatch redux a variable to use in parent component.
But I want to still open child component when I click on the button.
Child Component
const ServoActionFill = (e, jammerSelected) => {
dispatch({
type: "HIGHLIGHT_TARGET",
data: {
uniqueId: "546546644",
},
});
}
return <div>
{showPopup == true && target != null && subPopupServo == true &&
<div className="position-fixed d-flex" style={{ top: info.containerPoint.y - 130, left: info.containerPoint.x - 130, zIndex: 1000 }} onMouseLeave={() => disp("servo")}>
<PieMenu
radius="130px"
centerRadius="70px"
centerX={info.containerPoint.x}
centerY={info.containerPoint.y}
>
{/* Contents */}
{jammerList.activeJammers.map(x =>
//StatusName.forEach(y =>
// y == x.latinName ?
<Slice onMouseOver={(e) => ServoActionFill(e, x)} backgroundColor="#6b22f3"><div style={{ fontSize: "11px", fontWeight: "bold" }}>{x.latinName}</div></Slice>
)}
</PieMenu>
</div>
}
Parent Component
const handlePropChange = (prevProps, nextProps) => {
let returnState = true;
// filter targets based on types
if (nextProps.HIGHLIGHT_TARGET) {
filterTargetsBasedOnTypes(nextProps.targetTypeHiddenInfo);
}
};
const LeafletMap = React.memo(({ highlightedTarget }) => {
const [viewport, setViewport] = React.useState(DEFAULT_VIEWPORT)
const { props: { mapUrl } } = useTheme();
return (
<>
<TextField
classes={{ root: "target-search" }}
placeholder="Search Target"
title="searching target based on its id"
variant="filled"
type="search"
onChange={searchTargetHandler}
onKeyPress={(e) => {
if (e.key === 'Enter')
searchTargetBtnHandler()
}}
InputProps={{
classes: {
input: "py-2 text-black non-opacity",
},
endAdornment: <IconButton
className="p-0"
onClick={searchTargetBtnHandler}
>
<AiOutlineSearch />
</IconButton>
}}
/>
</>
);
}, handlePropChange);
const mapStateToProps = state => {
return {
highlightedTarget: state.BaseReducers.highlightedTarget,
};
};
export default connect(mapStateToProps)(LeafletMap);
When dispatch fire in Child component (Highlight_Target), Parent re-render by redux. But I want hold open Child component. Any solution?
I have this code which his basic todo list that I am trying to build in react
const Home = (props) => {
const classes = useStyles();
const [addNewTask, setAddNewTask] = useState(false);
const [formData, setFormData] = React.useState({ taskText: "" });
const dispatch = useDispatch();
useEffect(() => {
dispatch(getTasks());
}, [dispatch]);
const todos = useSelector((state) => state.todos);
const handleAddNew = () => {
setAddNewTask(addNewTask ? false : true);
};
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
dispatch(createTask(formData));
dispatch(getTasks())
};
return (
<Grow in>
<Container>
<Grid
className={classes.adjustTop}
container
justify="space-between"
alignItems="stretch"
spacing={3}
>
<Grid item xs={2} sm={2}></Grid>
<Grid item xs={8} sm={8}>
<Typography variant="h2">TODO LIST</Typography>
{todos==null?
<Typography>Loading...</Typography>
:
<>
{todos?.tasks?.map((todo) => (
<Task id={todo.task} todo={todo} />
))}
</>
}
<div style={{ textAlign: "center" }}>
{addNewTask ? (
<>
<Button
variant="contained"
size="large"
color="error"
style={{ marginTop: "10px" }}
onClick={handleAddNew}
>
Cancel Adding task
</Button>
<form style={{ paddingTop: "20px" }} onSubmit={handleSubmit}>
<Input
name="taskText"
label="Task Text"
handleChange={handleChange}
type="text"
placeholder="text"
/>
<br />
<Button type="submit">Submit</Button>
</form>
</>
) : (
<Button
variant="contained"
size="large"
color="primary"
style={{ marginTop: "10px" }}
onClick={handleAddNew}
>
+ Add new task
</Button>
)}
</div>
</Grid>
<Grid item xs={2} sm={2}></Grid>
</Grid>
</Container>
</Grow>
);
};
export default Home;
This is the reducer
import { FETCH_ALL,CREATE } from '../constants/actionTypes';
export default (state = {tasks:null}, action) => {
switch (action.type) {
case FETCH_ALL:
return {...state,tasks:action.payload,errors:null}
case CREATE:
return {...state,tasks:action.payload,errors:null}
default:
return state;
}
};
& actions
import { FETCH_ALL,CREATE} from '../constants/actionTypes';
import * as api from '../api/index.js';
export const getTasks = () => async (dispatch) => {
try {
const { data } = await api.fetchTasks();
console.log(data);
dispatch({ type: FETCH_ALL, payload: data });
} catch (error) {
console.log(error.message);
}
};
export const createTask = (taskText) => async (dispatch) => {
try {
const { data } = await api.createTask(taskText);
dispatch({type:CREATE,payload:data})
} catch (error) {
console.log(error.message);
}
};
I am able to add the data to database on submit using handleSubmit tot the database but the issue is after each submit its giving me an error TypeError: _todos$tasks.map is not a function
I tried to handle this by using ternary operator & rendering Loading text on null & also have used chaining operator,but still getting same error
You are trying to get todos from your redux store
const todos = useSelector((state) => state.todos);
But there is no todos in your state and plus you are updating another state tasks in your reducer:
export default (state = {tasks:null}, action) => {
switch (action.type) {
case FETCH_ALL:
return {...state,tasks:action.payload,errors:null}
case CREATE:
return {...state,tasks:action.payload,errors:null}
default:
return state;
}
};
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.
I have created a common table component in which I am calling in the file that are needed under some table there is an field action which have an edit and delete icon whenever I click on edit icon it should trigger a dropdown that will allow to update the status of component. But when I click on edit icon the table component will refresh so the menu is not opening at suitable position. Please help me
TableComponent
import React, { useState } from 'react';
import { Table, TableHead, TableRow, TableCell, makeStyles, TablePagination, TableSortLabel } from '#material-ui/core';
import PropTypes from 'prop-types';
const useStyles = makeStyles(theme => ({
table: {
marginTop: theme.spacing(3),
'& thead th': {
fontWeight: '600',
color:'white',
backgroundColor: '#143174',
},
'& tbody td': {
fontWeight: '300',
},
},
}))
export default function TableComponent(records, headCells,filterFn) {
const classes = useStyles();
const pages = [10, 50, 100]
const [page, setPage] = useState(0)
const [rowsPerPage, setRowsPerPage] = useState(pages[page])
const [order, setOrder] = useState()
const [orderBy, setOrderBy] = useState()
const TblContainer = props => (
<Table className={classes.table}>
{props.children}
</Table>
)
TblContainer.propTypes = {
children:PropTypes.element
}
const TblHead = props => {
const handleSortRequest = cellId => {
const isAsc = orderBy === cellId && order === "asc";
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(cellId)
}
return (<TableHead>
<TableRow>
{
headCells.map(headCell => (
<TableCell key={headCell.id}
sortDirection={orderBy === headCell.id ? order : false}>
{headCell.disableSorting ? headCell.label :
<TableSortLabel
active={orderBy === headCell.id}
direction={orderBy === headCell.id ? order : 'asc'}
onClick={() => { handleSortRequest(headCell.id) }}>
{headCell.label}
</TableSortLabel>
}
</TableCell>))
}
</TableRow>
</TableHead>)
}
const handleChangePage = (event, newPage) => {
setPage(newPage);
}
const handleChangeRowsPerPage = event => {
setRowsPerPage(parseInt(event.target.value, 10))
setPage(0);
}
const TblPagination = () => (<TablePagination
component="div"
page={page}
rowsPerPageOptions={pages}
rowsPerPage={rowsPerPage}
count={records.length}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
/>)
function stableSort(array, comparator) {
const stabilizedThis = array.map((el, index) => [el, index]);
stabilizedThis.sort((a, b) => {
const order = comparator(a[0], b[0]);
if (order !== 0) return order;
return a[1] - b[1];
});
return stabilizedThis.map((el) => el[0]);
}
function getComparator(order, orderBy) {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
function descendingComparator(a, b, orderBy) {
if (b[orderBy] < a[orderBy]) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
return 1;
}
return 0;
}
const recordsAfterPagingAndSorting = () => {
return stableSort(filterFn.fn(records), getComparator(order, orderBy))
.slice(page * rowsPerPage, (page + 1) * rowsPerPage)
}
return {
TblContainer,
TblHead,
TblPagination,
recordsAfterPagingAndSorting
}
}
ParentCoponent
import React,{ useState, useEffect, useCallback } from 'react';
import { Button, Typography, Grid, TextField, MenuItem, Menu, IconButton, Link, TableBody, TableRow, TableCell, Paper, makeStyles } from '#material-ui/core';
import Modals from './Components/Modals';
import DropDownComponent from './Components/DropDownComponent';
import PropTypes from 'prop-types';
import { fetchImage,postImage, removeImage, updateImageStatus } from './app/ActionCreator';
import { connect } from 'react-redux';
import EditStatusComponent from './Components/EditStatusComponent';
import { Edit, Delete } from '#material-ui/icons';
import TableComponent from './Components/TableComponent';
const styles = makeStyles(theme => ({
root:{
marginTop:'60px',
},
typo:{
flexGrow:1,
textAlign:'center',
marginTop:'30px',
color:'black'
},
headingpaper:{
width:'100%',
height:40,
marginTop:'30px',
background:'white',
borderColor:'white',
},
buttonColor:{
color:'white',
background:'#143174'
},
formControl: {
margin:'8px',
minWidth: 120,
},
selectEmpty: {
marginTop: '16px',
},
formRoot:{
margin:'8px',
width: '25ch',
},
modalButton:{
color:'white',
background:'#143174',
marginRight:'10px'
}
}))
const headCells = [
{id:1,label:'SlNo'},
{id:2,label:'Virtual Machine Image '},
{id:3,label:'createdAt'},
{id:4,label:'createdBy'},
{id:5,label:'IpAddress'},
{id:6,label:'Status'},
{id:7,label:'Action',disableSorting:true}
]
function AdminImages(props){
const [imageModal,setImageModal] = useState(false)
const [name,setName] = useState('')
const [cpu,setCpu] = useState('')
const [ram,setRam] = useState('')
const [disk,setDisk] = useState('')
const [imageid,setImageid] = useState('')
const [anchorEl,setAnchorEl] = useState(null)
const [filterFn, setFilterFn] = useState({ fn: items => { return items; } })
const handlecpuChange = (event) => {
setCpu(event.target.value)
}
const handleramChange = (event) => {
setRam(event.target.value)
}
const handlediskChange = (event) => {
setDisk(event.target.value)
}
useEffect(() => {
props.fetchImageData()
},[])
const handleMenu = useCallback((event,id) => {
setAnchorEl(event.currentTarget)
setImageid(id)
},[])
const handleClose = (ev) => {
setAnchorEl(null)
if(ev.target.innerText !== ''){
const data = {
"status":ev.target.innerText
}
props.updateImageStatusData(imageid,data)
}
}
const newInstance = () => {
if(name !== '' && cpu !== '' && ram !== '' && disk !== ''){
props.postImageData(name,cpu,ram,disk)
}
else {
alert("Please Enter All Field")
}
setImageModal(false)
}
const labstatus = (status) => {
if(status === 'Resuming' || status === 'Running'){
return(
<EditStatusComponent
status={status}
statuscolor='#32CD32'
showDropDown={false}/>
)
}
else if(status === 'Suspending' || status === 'suspending' || status === 'error'){
return(
<EditStatusComponent
status={status}
statuscolor='red'
showDropDown={false}/>
)
}
else{
return(
<EditStatusComponent
status={status}
statuscolor='#143174'
showDropDown={false}/>
)
}
}
const handleSearch = e => {
let target = e.target;
setFilterFn({
fn: items => {
if (target.value == "")
return items;
else
return items.filter(x => x.instance_name.toLowerCase().includes(target.value))
}
})
}
const classes = styles()
const { TblContainer, TblHead, TblPagination, recordsAfterPagingAndSorting } = TableComponent(props.Images.images,headCells,filterFn)
return(
<div className={classes.root}>
<Grid container direction="row" justify="flex-end" alignItems="flex-start">
<Button variant="contained" className={classes.buttonColor} onClick={() => setImageModal(true)}>
Create Images
</Button>
</Grid>
<Typography variant="h2" noWrap className={classes.typo}>
Virtual Machine Image
</Typography>
<Paper>
<TblContainer>
<TblHead/>
<TableBody>
{
recordsAfterPagingAndSorting().map((v,i) => (
<TableRow key={v.id}>
<TableCell>{v.id}</TableCell>
<TableCell>{v.instance_name}</TableCell>
<TableCell>{v.created_at}</TableCell>
<TableCell>{v.created_by.first_name + v.created_by.last_name}</TableCell>
<TableCell><Link target={"_blank"} onClick={() => window.open(`http://${v.ip_address}/project`,'_blank')}>{v.ip_address}</Link></TableCell>
<TableCell>{labstatus(v.status)}</TableCell>
<TableCell>
<div style={{display:'flex'}}>
<IconButton
aria-controls="simple-menu"
aria-haspopup="true"
onClick={(e)=> handleMenu(e,v.id)}>
<Edit/>
</IconButton>
<IconButton onClick={() => props.removeImageData(v.id)}>
<Delete/>
</IconButton>
</div>
</TableCell>
</TableRow>
))
}
</TableBody>
</TblContainer>
<TblPagination/>
</Paper>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}>
<MenuItem value="Resume" onClick={handleClose}>Resume</MenuItem>
<MenuItem value="Suspend" onClick={handleClose}>Suspend</MenuItem>
<MenuItem value="Freeze" onClick={handleClose}>Freeze</MenuItem>
</Menu>
<div>
<Modals
show={imageModal}
title="Create Instance"
handleClose={() => setImageModal(true)}
showSubmit={true}
onPress={newInstance}
size="xs">
<TextField
id="standard-basic"
label="Name"
style={{width:'50ch',margin:10}}
value={name}
onChange={(e) => setName(e.target.value)}
InputLabelProps={{
shrink: true,
}}/>
<DropDownComponent
dropdownid="standard-select-cpu"
selectedValue={cpu}
handleChange={handlecpuChange}
dropdownLabel="CPU">
<MenuItem value="8">8</MenuItem>
<MenuItem value="10">10</MenuItem>
<MenuItem value="12">12</MenuItem>
<MenuItem value="14">14</MenuItem>
<MenuItem value="16">16</MenuItem>
<MenuItem value="18">18</MenuItem>
<MenuItem value="20">20</MenuItem>
<MenuItem value="22">22</MenuItem>
<MenuItem value="24">24</MenuItem>
<MenuItem value="26">26</MenuItem>
<MenuItem value="28">28</MenuItem>
<MenuItem value="30">30</MenuItem>
<MenuItem value="32">32</MenuItem>
</DropDownComponent>
<DropDownComponent
dropdownid="standard-select-ram"
selectedValue={ram}
handleChange={handleramChange}
dropdownLabel="RAM">
<MenuItem value="16">16</MenuItem>
<MenuItem value="20">20</MenuItem>
<MenuItem value="24">24</MenuItem>
<MenuItem value="28">28</MenuItem>
<MenuItem value="32">32</MenuItem>
<MenuItem value="36">36</MenuItem>
<MenuItem value="40">40</MenuItem>
<MenuItem value="44">44</MenuItem>
<MenuItem value="48">48</MenuItem>
<MenuItem value="52">52</MenuItem>
<MenuItem value="56">56</MenuItem>
<MenuItem value="60">60</MenuItem>
<MenuItem value="64">64</MenuItem>
</DropDownComponent>
<DropDownComponent
dropdownid="standard-select-disk"
selectedValue={disk}
handleChange={handlediskChange}
dropdownLabel="Disk">
<MenuItem value="50">50</MenuItem>
<MenuItem value="100">100</MenuItem>
<MenuItem value="150">150</MenuItem>
<MenuItem value="200">200</MenuItem>
<MenuItem value="250">250</MenuItem>
<MenuItem value="300">300</MenuItem>
<MenuItem value="350">350</MenuItem>
<MenuItem value="400">400</MenuItem>
<MenuItem value="450">450</MenuItem>
<MenuItem value="500">500</MenuItem>
</DropDownComponent>
</Modals>
</div>
</div>
)
}
const mapStateToProps = state => {
return {
Images:state.Images,
}
}
const mapDispatchToProps = dispatch => ({
fetchImageData:() => {
dispatch(fetchImage())
},
postImageData:(name,cpu,ram,storage) => {
dispatch(postImage(name,cpu,ram,storage))
},
removeImageData:(id) => {
dispatch(removeImage(id))
},
updateImageStatusData:(id,data) => {
dispatch(updateImageStatus(id,data))
}
})
AdminImages.propTypes = {
classes:PropTypes.object.isRequired,
Images:PropTypes.object,
fetchImageData:PropTypes.func,
postImageData:PropTypes.func,
removeImageData:PropTypes.func,
updateImageStatusData:PropTypes.func,
}
export default connect(mapStateToProps,mapDispatchToProps)(AdminImages)
You can prevent unnecessary re-renders of a child component in 2 ways
If your component is class based then you can extend React.PureComponent or implement shouldComponentUpdate lifecycle method by yourself.
class MyComponent extends React.PureComponent {
// your component logic
}
class MyComponent extends Component {
shouldComponentUpdate(nextProps, nextState) {
//compare nextProps and this.props
//compare nextState and this.state
//return true for re-render, otherwise false
}
// your component logic
}
If you have a functional component you can wrap it with memo function that will check only for prop changes or you can pass it a function in the second argument that will do the current props and next props comparison and return true/false if you want do manual comparison just like shouldComponentUpdate
const MyComponent = React.memo(function(props) {
/* render using props */
});
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
Here are some useful links from the docs:
class component
function component