I am having a problem that I don't know where it cause. Please help me out.
I am developing a stock management platform, it allows users to CRUD products.
When Users are on the product page, they will see a table with edit and delete buttons in every record.
Whenever they click on the delete button, it will show a PopOver to confirm the delete action.
And the error happens when I click "Yes" in the PopOver Component
I am using Material-UI version 4.12.3,
Here is my parent component:
const useStyles = makeStyles((theme) => ({
modal: {
display: "flex",
alignItems: "center",
justifyContent: "center",
},
paper: {
backgroundColor: theme.palette.background.paper,
border: "2px solid #000",
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3),
},
loading: {
display: "flex",
justifyContent: "center",
},
}));
const useRowStyles = makeStyles({
root: {
"& > *": {
borderBottom: "100%",
},
},
});
function createData(id, name, supplier, type, unit, quantity, cost, price, ingredients, action) {
return {
id,
name,
supplier,
type,
unit,
quantity,
cost,
price,
ingredients,
action,
};
}
function Row(props) {
const { row } = props;
const [open, setOpen] = React.useState(false);
const classes = useRowStyles();
const isCocktail = (type) => {
if (type === "Cocktail" || type === "Mocktail") {
return true;
} else {
return false;
}
};
return (
<React.Fragment>
<TableRow className={classes.root}>
<TableCell>
{isCocktail(row.type) ? (
<IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}>
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
) : null}
</TableCell>
<TableCell component="th" scope="row">
{row.id}
</TableCell>
<TableCell align="middle">{row.name}</TableCell>
<TableCell align="middle">{row.supplier}</TableCell>
<TableCell align="middle">{row.type}</TableCell>
<TableCell align="middle">{row.unit}</TableCell>
<TableCell align="middle">{row.quantity}</TableCell>
<TableCell align="middle">{row.cost}</TableCell>
<TableCell align="middle">{row.price}</TableCell>
<TableCell align="middle">{row.action}</TableCell>
</TableRow>
<TableRow></TableRow>
</React.Fragment>
);
}
function Ingredients({ controller }) {
const classes = useStyles();
const dispatch = useDispatch();
const loading = useSelector((state) => state.product.loading);
const listProducts = useSelector((state) => state?.product?.products?.products);
const role = useSelector((state) => state.auth.user.role);
const [open, setOpen] = React.useState(false);
const nonCocktailList = listProducts?.filter((e) => e.type !== "Cocktail");
const nonMocktailList = nonCocktailList?.filter((e) => e.type !== "Mocktail");
const nonFoodList = nonMocktailList?.filter((e) => e.type !== "Food");
const handleOpen = async (id) => {
await dispatch(productActions.getSingleProduct(id));
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
let vietNamD = Intl.NumberFormat("vi-VI");
let rows = nonFoodList?.map((product, index) => {
return createData(
index,
product?.name,
product?.supplier,
product?.unit,
`${product?.capacity + " " + product?.capacityUnit}`,
`${vietNamD.format(product?.cost)} đ`,
`${product?.quantity + " " + product?.unit}`,
<div className="actions">
<Button variant="contained" style={{ backgroundColor: "#2EC0FF", color: "white" }} onClick={() => handleOpen(product?._id)}>
<EditOutlinedIcon style={{ color: "white" }} />
</Button>
<PopOver id={product._id} role={role} controller={controller} />
</div>
);
});
useEffect(() => {
dispatch(supplierActions.getSuppliers());
}, [dispatch]);
return loading ? (
<div className={classes.loading}>
{" "}
<CircularProgress />
</div>
) : (
<div className="create-button">
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
className={classes.modal}
open={open}
onClose={handleClose}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
<Fade in={open}>
<CreateUpdateIngredient handleClose={handleClose} controller={controller} />
</Fade>
</Modal>
<IngredientModal controller={controller} />
<TableContainer component={Paper}>
<Table aria-label="collapsible table">
<TableHead style={{ backgroundColor: "black", width: "100%" }}>
<TableRow>
<TableCell />
<TableCell style={{ color: "white" }}>Index</TableCell>
<TableCell style={{ color: "white" }} align="middle">
Name
</TableCell>
<TableCell style={{ color: "white" }} align="middle">
Supplier
</TableCell>
<TableCell style={{ color: "white" }} align="middle">
Unit
</TableCell>
<TableCell style={{ color: "white" }} align="middle">
Unit Capacity
</TableCell>
<TableCell style={{ color: "white" }} align="middle">
Cost
</TableCell>
<TableCell style={{ color: "white" }} align="middle">
Daily Quantity Needed
</TableCell>
<TableCell
style={{
color: "white",
display: "flex",
justifyContent: "center",
}}
align="middle"
>
Action
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows?.map((row) => (
<Row key={row.id} row={row} />
))}
</TableBody>
</Table>
</TableContainer>
</div>
);
}
export default React.memo(Ingredients);
And this is the PopOver:
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import Popover from "#material-ui/core/Popover";
import Typography from "#material-ui/core/Typography";
import { Button, IconButton } from "#material-ui/core";
import DeleteOutlineOutlinedIcon from "#material-ui/icons/DeleteOutlineOutlined";
import { useDispatch } from "react-redux";
import { productActions } from "redux/actions";
const useStyles = makeStyles((theme) => ({
typography: {
padding: theme.spacing(2),
},
}));
export default function PopOver(props) {
const { controller, id, role } = props;
const classes = useStyles();
const dispatch = useDispatch();
const [anchorEl, setAnchorEl] = React.useState(null);
const deleteProduct = (id) => {
if (!id) return;
dispatch(productActions.deleteProduct(id));
dispatch(productActions.getProducts(controller));
handleClose();
};
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const productId = open ? "simple-popover" : undefined;
return (
<div>
<Button aria-describedby={id} style={{ backgroundColor: "#FF4D4F", height: "40px", width: "20px" }} variant="contained" onClick={handleClick} disabled={role !== "Admin"}>
<IconButton aria-label="delete" color="secondary">
<DeleteOutlineOutlinedIcon style={{ color: "white" }} />
</IconButton>
</Button>
<Popover
id={productId}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
>
<Typography className={classes.typography}>
Do you want to delete this ingredient?
<Button
color="primary"
onClick={() => {
deleteProduct(id);
}}
>
Yes
</Button>
<Button color="secondary" onClick={handleClose}>
No
</Button>
</Typography>
</Popover>
</div>
);
}
These are the two actions that called when a record is deleted
const getProducts =
({ date, mode }) =>
async (dispatch) => {
const body = { date, mode };
dispatch({ type: types.GET_PRODUCTS_REQUEST, payload: null });
try {
const res = await api.post("/products", body);
dispatch({ type: types.GET_PRODUCTS_SUCCESS, payload: res.data.data });
} catch (error) {
toast.error("Get Product List Failed");
dispatch({ type: types.GET_PRODUCTS_FAILURE, payload: error });
}
};
const deleteProduct = (id) => async (dispatch) => {
dispatch({ type: types.DELETE_PRODUCT_REQUEST, payload: null });
try {
const res = await api.delete(`/products/${id}`);
dispatch({ type: types.DELETE_PRODUCT_SUCCESS, payload: res.data.data });
toast.success("Delete Product Success");
} catch (error) {
toast.error("Delete Product Fail");
dispatch({ type: types.DELETE_PRODUCT_FAILURE, payload: error });
}
};
Related
I can't convert the api data into raw data on table. the data I can display on my console but don't know how to get it on the table.
here is my code for fetching dummy api data:-
const paginationTable = () => {
const [data, setData] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((res) => {
// console.log(res);
setData(res);
});
}, []);
console.log(data);
// data.map((item) => {
// console.log(item.id);
// });
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(5);
const columns = [
{
title: "ID",
field: data.id,
editable: false,
headerStyle: {
color: "white",
backgroundColor: "hwb(12deg 0% 0% / 86%)",
},
},
{
title: "Station Name",
field: data.address.street,
headerStyle: {
color: "white",
backgroundColor: "hwb(12deg 0% 0% / 86%)",
},
},
{
title: "Location",
field: data.address.city,
headerStyle: {
color: "white",
backgroundColor: "hwb(12deg 0% 0% / 86%)",
},
},
{
title: "Status",
field: data.website,
headerStyle: {
color: "white",
backgroundColor: "hwb(12deg 0% 0% / 86%)",
},
},
];
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(+event.target.value);
setPage(0);
};
return (
<>
{/* <APIdata/> */}
<Paper sx={{ width: "100%" }}>
<TableContainer sx={{ maxHeight: 440 }}>
<Table>
<TableHead>
<TableRow>
{columns.map((column) => (
<TableCell
key={column.title}
align={column.align}
style={{ top: 57, minWidth: column.minWidth }}
sx={{ fontSize: "1rem" }}
>
{column.title}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{data
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row, i) => {
return (
<TableRow hover role="checkbox" tabIndex={-1} key={i}>
{/* updated
<TableRow hover role="checkbox" tabIndex={-1} key={row.code}>
*/}
{columns.map((column) => {
const value = column.title;
// console.log(value);
return (
<>
<TableCell key={column} align={column.align}>
{column.format && typeof value === "number"
? column.format(value)
: value}
</TableCell>
</>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25, 100]}
component="div"
count={data.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
</>
);
};
export default paginationTable;
the data fetched from api is something like this:-
currently I'm getting a blank web page.If i replace field values with
field: data.address instead of data.address.city and data.address.street it gives me table with headings only.. like this
I tried using "material-table" and through that I was able to get the data on the table but I've to add a view button over there in each row so I'm using material-ui table for that so that I can use material-icons
Facing this issue where the React Collapsible Table (Material UI) does not seem to reflect with data from the API after I click the next page button on my react table.
When I load the page initially, it reflects with data from the first page of server side paginated data. (see below)
first-page
When I click on the next page, the table shows up blank.
second-page
Here is the weird bit. When I navigate to the third page, the height of the table area doubles and is still blank.
third-page
Here is the code. In a nutshell, I'm iterating through the column names and column values for the jobs and the runs and displaying that.
function Row(props) {
const { row, rows } = props;
// console.log(rows);
// console.log(Object.keys(row))
const [open, setOpen] = React.useState(false);
return (
<React.Fragment>
<TableRow sx={{ "& > *": { borderBottom: "unset" } }}>
<TableCell>
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
>
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
</TableCell>
{Object.values(row).map((colVal, counter) => {
if (typeof colVal != "object") {
// console.log(counter);
// console.log(colVal);
if (counter == 0) {
return (
<TableCell key={counter} component="th" scope="row">
{colVal}
</TableCell>
);
} else {
return (
<TableCell
key={counter}
align="right"
style={{
whiteSpace: "normal",
wordWrap: "break-word",
maxWidth: "150px",
}}
>
{colVal}
</TableCell>
);
}
}
})}
</TableRow>
<TableRow
style={{
backgroundColor: "rgb(0, 127, 97,0.2)",
}}
>
<TableCell
style={{
paddingBottom: 0,
paddingTop: 0,
}}
colSpan={Object.keys(rows[0]).length}
>
<Collapse
in={open}
timeout="auto"
// unmountOnExit
>
<Box
sx={{
margin: 1,
maxHeight: 200,
overflow: "auto",
}}
>
<Typography variant="h6" gutterBottom component="div">
Runs
</Typography>
<Table size="small" aria-label="runs">
<TableHead>
<TableRow>
{Object.keys(rows[0].runs[0]).map(
(runColName, runColNameCounter) => {
if (runColNameCounter) {
// console.log(runColNameCounter);
// console.log(runColName);
if (runColNameCounter == 0) {
return (
<TableCell key={runColNameCounter}>
{runColName}
</TableCell>
);
} else {
return (
<TableCell
key={runColNameCounter}
style={{
whiteSpace: "normal",
wordWrap: "break-word",
}}
align="right"
>
{runColName}
</TableCell>
);
}
}
}
)}
{/* <TableCell>Date</TableCell> */}
{/* <TableCell>Run ID</TableCell> */}
{/* <TableCell align="right">Amount</TableCell> */}
{/* <TableCell align="right">Total price ($)</TableCell> */}
</TableRow>
</TableHead>
<TableBody>
{row.runs.map((runRow) => (
<TableRow
sx={{
borderTop: "2px solid #686868",
// borderBottom: "2px solid black",
}}
key={runRow.run_id}
>
{Object.values(runRow).map((runColVal, runCounter) => {
// console.log(runCounter);
// console.log(runColVal);
if (runCounter == 0) {
return (
<TableCell
key={runCounter}
component="th"
scope="row"
>
{runColVal}
</TableCell>
);
} else {
return (
<TableCell
key={runCounter}
align="right"
style={{
whiteSpace: "normal",
wordWrap: "break-word",
}}
>
{runColVal}
</TableCell>
);
}
})}
</TableRow>
))}
</TableBody>
</Table>
</Box>
</Collapse>
</TableCell>
</TableRow>
</React.Fragment>
);
}
// Row.propTypes = {
// row: PropTypes.shape({
// calories: PropTypes.number.isRequired,
// carbs: PropTypes.number.isRequired,
// fat: PropTypes.number.isRequired,
// history: PropTypes.arrayOf(
// PropTypes.shape({
// amount: PropTypes.number.isRequired,
// customerId: PropTypes.string.isRequired,
// date: PropTypes.string.isRequired,
// }),
// ).isRequired,
// name: PropTypes.string.isRequired,
// price: PropTypes.number.isRequired,
// protein: PropTypes.number.isRequired,
// }).isRequired,
// };
function TablePaginationActions(props) {
const theme = useTheme();
const { count, page, rowsPerPage, onPageChange } = props;
const handleFirstPageButtonClick = (event) => {
onPageChange(event, 0);
};
const handleBackButtonClick = (event) => {
onPageChange(event, page - 1);
};
const handleNextButtonClick = (event) => {
onPageChange(event, page + 1);
};
const handleLastPageButtonClick = (event) => {
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
};
return (
<Box sx={{ flexShrink: 0, ml: 2.5 }}>
<IconButton
onClick={handleFirstPageButtonClick}
disabled={page === 0}
aria-label="first page"
>
{theme.direction === "rtl" ? <LastPageIcon /> : <FirstPageIcon />}
</IconButton>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
>
{theme.direction === "rtl" ? (
<KeyboardArrowRight />
) : (
<KeyboardArrowLeft />
)}
</IconButton>
<IconButton
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
>
{theme.direction === "rtl" ? (
<KeyboardArrowLeft />
) : (
<KeyboardArrowRight />
)}
</IconButton>
<IconButton
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
>
{theme.direction === "rtl" ? <FirstPageIcon /> : <LastPageIcon />}
</IconButton>
</Box>
);
}
TablePaginationActions.propTypes = {
count: PropTypes.number.isRequired,
onPageChange: PropTypes.func.isRequired,
page: PropTypes.number.isRequired,
rowsPerPage: PropTypes.number.isRequired,
};
export default function CollapsibleTable() {
let params = useParams();
let dispatch = useDispatch();
const [loading, setLoading] = React.useState(false);
// const allJobss = useSelector((state) => console.log(state));
const allJobsStatus = useSelector((state) => state.jobs.status);
const rows = useSelector((state) => state.jobs.jobs);
// const [success, setSuccess] = React.useState(false);
const kpiId = params.id;
const fromDateStr = window.sessionStorage.getItem("fromDate");
const toDateStr = window.sessionStorage.getItem("toDate");
var totalEntries = useSelector((state) => state.jobs.totalEntries);
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(5);
// Avoid a layout jump when reaching the last page with empty rows.
const emptyRows =
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0;
const getJobsDispatch = (pageNum = 1) => {
if (!loading) {
// setSuccess(false);
console.log("loading");
setLoading(true);
dispatch(getJobs({ kpiId, fromDateStr, toDateStr, pageNum })).then(
(action) => {
try {
setLoading(false);
console.log("stop loading");
// setRows(action.payload.resp.allJobs);
} catch (e) {
console.log(e);
}
}
);
// console.log(allJobs);
// setRows(allJobs);
}
};
const handleChangePage = (event, newPage) => {
setPage(newPage);
var pageNum = newPage + 1;
getJobsDispatch(pageNum);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
useEffect(() => {
getJobsDispatch();
return () => {
// console.log(allJobs);
};
}, []);
return (
<TableContainer component={Paper}>
{loading && (
<CircularProgress
size={68}
sx={{
// color: green[500],
position: "absolute",
top: "50%",
left: "50%",
zIndex: 1,
}}
/>
)}
<Table aria-label="collapsible table">
<TableHead>
<TableRow>
<TableCell />
{Object.keys(rows[0]).map((colName, colNameCounter) => {
// console.log(rows);
if (typeof rows[0][colName] != "object") {
// console.log(colNameCounter);
// console.log(colName);
if (colNameCounter == 0) {
return <TableCell key={colNameCounter}>{colName}</TableCell>;
} else {
return (
<TableCell
key={colNameCounter}
style={{
whiteSpace: "normal",
wordWrap: "break-word",
maxWidth: "150px",
}}
align="right"
>
{colName}
</TableCell>
);
}
}
})}
</TableRow>
</TableHead>
<TableBody>
{(rowsPerPage > 0
? rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
: rows
).map((row) => (
<Row key={row.name} row={row} rows={rows} />
))}
{emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[]}
colSpan={3}
count={parseInt(totalEntries)}
rowsPerPage={5}
page={page}
SelectProps={{
inputProps: {
"aria-label": "rows per page",
},
native: true,
}}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
ActionsComponent={TablePaginationActions}
/>
</TableRow>
</TableFooter>
</Table>
</TableContainer>
);
}
So I'm almost complete on my MERN with Redux/Toolkit, I've done Create, Read, and Update. I can also delete a product, but it doesn't function the way I wanted to.
So what happen is, when I delete the item1 It will delete the last row of the table.
in this image, I will try to delete the item1
Before I click
After clicking the item1, it removes the item 3
After I click
But when I refresh it, It corrects itself
After refresh/reload
apiCalls.js
export const deleteProduct = async (id, dispatch) => {
dispatch(deleteProductStart());
try {
const res = await publicRequest.delete(`/products/${id}`);
dispatch(deleteProductSuccess(res.data));
} catch (err) {
dispatch(deleteProductFailure());
}
};
MyProduct.js
const MyProducts = () => {
const {products, isFetching, isError} = useSelector((state) => state.product)
const dispatch = useDispatch()
const handleDelete = (id) => {
deleteProduct(id, dispatch);
};
useEffect(() =>{
getProduct(dispatch);
},[dispatch])
return (
<Box>
<Navbar />
<Box sx={{display: {xs: 'none', md: 'flex'}, padding: '20px', flexDirection: 'column'}}>
<Typography variant="h5" fontWeight={700}>My Products</Typography>
<TableContainer component={Paper}>
<Table sx={{ minWidth: 600 }} aria-label="simple table">
<TableHead>
<TableRow bgcolor="skyblue">
<TableCell align='center'>Product</TableCell>
<TableCell align='center'>Image</TableCell>
<TableCell align='center'>Price</TableCell>
<TableCell align='center'>Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{products.map((product) => (
<TableRow
key={product._id}
>
<TableCell align='center' component="th" scope="row">{product.title}</TableCell>
<TableCell align='center'><Box component="img" sx={{width: '80px', height: '80px', borderRadius: '10px', objectFit: 'contain'}} src={product.productImage}/></TableCell>
<TableCell align='center'>Php {product.price}</TableCell>
<TableCell align='center' bgcolor="skyblue" >
<Box sx={{display: 'flex', alignItems:'center', justifyContent: 'center', gap: "10px"}}>
<Link to={`/editproduct/${product._id}`}>
<Button variant="contained">Edit</Button>
</Link>
<Button onClick={() => handleDelete(product._id)} color="error" variant="contained">Delete</Button>
</Box>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Box>
</Box>
)
}
productSlice.js
import { createSlice } from "#reduxjs/toolkit";
export const productSlice = createSlice({
name: 'product',
initialState: {
products: [],
isFetching: false,
isError: false
},
reducers: {
deleteProductStart: (state) => {
state.isFetching = true
state.isError = false
},
deleteProductSuccess: (state, action) => {
state.isFetching = false;
state.products.splice(
state.products.findIndex((item) => item._id === action.payload),
1
);
},
deleteProductFailure: (state) => {
state.isFetching = false
state.isError = true
},
}
})
export const {
deleteProductStart,
deleteProductSuccess,
deleteProductFailure,
} = productSlice.actions
export default productSlice.reducer
deleteProductSuccess: (state, action) => {
state.isFetching = false;
state.products.splice(
state.products.findIndex((item) => item._id === action.payload),
1
);
},
In this code block you are finding product by index considering action.payload as item id. And you are passing data from response as action.
dispatch(deleteProductSuccess(res.data));
So item._id === action.payload will always fails. findByIndex will return -1. splice(-1, 1) will remove the last element.
Fixing the action.payload will fix this issue.
You have to consider the scenario when item not exist. Currently it is removing last item.
Like the title says I need to change the page based on search value. What do I mean by this, let's say that the name Test is on th page 2 of my table, but not on page 1. How do I then change the page automatically?
Here is my TableComponent.js:
import React, { useState } from "react";
import Table from "#mui/material/Table";
import TableBody from "#mui/material/TableBody";
import TableCell from "#mui/material/TableCell";
import TableContainer from "#mui/material/TableContainer";
import TableHead from "#mui/material/TableHead";
import TableRow from "#mui/material/TableRow";
import useFetch from "./custom_hooks/useFetch";
import TableFooter from "#mui/material/TableFooter";
import TablePagination from "#mui/material/TablePagination";
import IconButton from "#mui/material/IconButton";
import Box from "#mui/material/Box";
import { MdKeyboardArrowRight, MdKeyboardArrowLeft } from "react-icons/md";
import styled from "styled-components";
const TableH = styled(TableHead)`
height: 3.5rem;
`;
const TableB = styled(TableBody)`
height: 3.375rem;
`;
const RoleButton = styled.button`
color: #c80539;
outline: none;
border: none;
background: none;
cursor: pointer;
font-size: 14px;
font-weight: normal;
font-stretch: normal;
font-style: normal;
line-height: 1.43;
letter-spacing: normal;
`;
function TablePaginationActions(props) {
const { page, onPageChange } = props;
const handleBackButtonClick = (event) => {
onPageChange(event, page);
};
const handleNextButtonClick = (event) => {
onPageChange(event, page + 2);
};
return (
<Box sx={{ flexShrink: 0, ml: 2.5 }}>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
>
<MdKeyboardArrowLeft color="#707070" />
</IconButton>
<IconButton onClick={handleNextButtonClick} aria-label="next page">
<MdKeyboardArrowRight color="#707070" />
</IconButton>
</Box>
);
}
const TableComponent = ({ url, cellNames, value }) => {
const [page, setPage] = useState(1);
const [rowsPerPage, setRowsPerPage] = useState(5);
const [roles, setRoles] = useState("Admin");
const { data, error } = useFetch(`${url}${page}`);
if (!data) {
return <h1> LOADING...</h1>;
}
if (error) {
console.log(error);
}
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10));
};
// currently this is not functional as it is supposed to be, right now when you click on a button it is going to change role for everyone
const handleRoleChange = () => {
if (roles === "Admin") {
setRoles("User");
} else {
setRoles("Admin");
}
};
return (
<TableContainer
style={{
height: 385,
border: "1px solid #e2e2e3",
borderRadius: 5,
}}
data-testid="table-container"
>
<Table stickyHeader>
<TableH>
<TableRow>
{cellNames.map((names, index) => {
return (
<TableCell
style={{
fontWeight: "bold",
fontSize: 14,
paddingRight: names.paddingRight,
paddingLeft: names.paddingLeft,
}}
align={names.align}
key={index}
>
{names.name}
</TableCell>
);
})}
</TableRow>
</TableH>
<TableB>
{data.data
.filter((val) => {
const notFound = !val.name
.toLowerCase()
.includes(value.toLowerCase());
if (value === "") {
return val;
} else if (val.name.toLowerCase().includes(value.toLowerCase())) {
return val;
} else if (notFound) {
// WHAT TO DO HERE ????
}
})
.slice(0, rowsPerPage)
.map((apiData, index) => {
return (
<TableRow key={index}>
<TableCell align="left" style={{ paddingLeft: 40 }}>
{apiData.name}
</TableCell>
<TableCell align="left">{apiData.email}</TableCell>
<TableCell align="left">{apiData.status}</TableCell>
<TableCell align="left">{roles}</TableCell>
<TableCell align="right" style={{ paddingRight: 40 }}>
<RoleButton onClick={handleRoleChange}>
{roles === "Admin" ? "Set as User" : "Set as Admin"}
</RoleButton>
</TableCell>
</TableRow>
);
})}
</TableB>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[5, 10]}
colSpan={6}
style={{
borderBottom: "none",
height: 50,
color: "#707070",
}}
count={data.meta.pagination.total}
rowsPerPage={rowsPerPage}
page={page - 1}
SelectProps={{
inputProps: {
"aria-label": "rows per page",
},
native: true,
}}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
ActionsComponent={TablePaginationActions}
/>
</TableRow>
</TableFooter>
</Table>
</TableContainer>
);
};
export default TableComponent;
If anyone know how to do this, I would greatly appreciate that. As I only recently started using Material UI, this seems complicated for me. I assume that somehow I need to change page based on condition that the value is not found on current page, but how do I implement that?
I am getting the following error:
Error: Maximum update depth exceeded.
With this component:
const UsersPage: React.FunctionComponent<UsersPageProps> = ({
location,
history,
match,
}): JSX.Element => {
const dispatch = useDispatch();
const { search } = location;
const query = parseInt(location.search, 10);
const [currentPage, setCurrentPage] = useState<number>(
isNaN(query) || query < 0 ? 0 : query
);
const users = useSelector((state: any) => state.manageUsers.users.results);
const isLoading = useSelector((state: any) => state.manageUsers.usersLoaded);
const totalPages = useSelector(
(state: any) => state.manageUsers.users.totalPages
);
const handlePaginationChange = (
event: React.ChangeEvent<any>,
pageNumber: number
): void => {
history.push(`${match.path}?page=${pageNumber ?? 0}`);
setCurrentPage(pageNumber ?? 0);
};
const handleAddUserOnClick = () =>
dispatch(manageUsersSetNewUserAndNavigate());
const handleEditOnClick = (id) =>
dispatch(manageUsersSetActiveEditUserAndNavigate(id));
useEffect(() => {
dispatch(manageUsersLoadUsers(currentPage));
}, [currentPage]);
return (
<section className="users page">
{!isLoading && <SectionLoaderWithMessage message={"Loading users..."} />}
{isLoading && (
<React.Fragment>
<Toolbar disableGutters={true}>
<Title flex={1}>Manage Users</Title>
<Button
variant="contained"
color="secondary"
onClick={handleAddUserOnClick}
startIcon={<AddCircle />}
>
Add User
</Button>
</Toolbar>
<TableContainer component={Paper}>
<Table aria-label="table">
<TableHead>
<TableRow style={{ backgroundColor: "white" }}>
<TableCell style={{ fontWeight: "bold", minWidth: 150 }}>
Email
</TableCell>
<TableCell style={{ fontWeight: "bold", minWidth: 150 }}>
Name
</TableCell>
<TableCell style={{ fontWeight: "bold", minWidth: 150 }}>
Surname
</TableCell>
<TableCell style={{ fontWeight: "bold", minWidth: 150 }}>
Firm
</TableCell>
<TableCell style={{ fontWeight: "bold", minWidth: 150 }}>
Type
</TableCell>
<TableCell style={{ fontWeight: "bold", minWidth: 150 }}>
Enabled
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{users.map((user, index) => (
<TableRow
key={`${user.id}-${index}`}
onClick={() => handleEditOnClick(user.id)}
hover
>
<TableCell>{user.email}</TableCell>
<TableCell>{user.name}</TableCell>
<TableCell>{user.surname}</TableCell>
<TableCell>{user.firmName}</TableCell>
<TableCell>{user.type}</TableCell>
<TableCell>{`${user.enabled}`}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<ThemedTablePagination
totalPages={totalPages}
pageNumber={currentPage}
onChange={handlePaginationChange}
/>
</TableContainer>
</React.Fragment>
)}
</section>
);
};
export default UsersPage;
I do not see anywhere in the render that is updating the state to cause an infinite loop.
The function handlePaginationChange should be inside useEffect.
Currently each time setCurrentPage is called it re-render the component which in-turn calls the handlePaginationChange function again, hence calling setCurrentPage again and the cycle goes on and on.