React Material UI Table - Switches all toggle at the same time - javascript

I have added a react mat ui table to my app and added switches into one of the columns but for some reason they all toggle together rather than independently.
How can I change this?
<TableBody>
{operators.map((row) => (
<TableRow key={row.key}>
<TableCell component="th" scope="row">
{row.operator}
</TableCell>
<TableCell>
<Typography component="div">
<Grid
component="label"
container
alignItems="center"
// justify="flex-end"
spacing={1}
>
<Grid item>Off</Grid>
<Grid item>
<AntSwitch
checked={checkboxState.checkedC}
onChange={handleChange}
name="checkedC"
/>
</Grid>
<Grid item>On</Grid>
</Grid>
</Typography>
</TableCell>
<TableCell align="right">{'<<Placement value>>'}</TableCell>
</TableRow>
))}
</TableBody>
I just created a demo in a sandbox to show you but the strange thing is that it works correctly there but not in my app.
The full code is in my Gist: https://gist.github.com/SerdarMustafa1/b3214b01885980e433405987f8822fe7
and demo: https://stackblitz.com/edit/5dm8lk?file=demo.js.
Any ideas what's gone wrong?

You can use array of states to keep the state of each raw:
import React, {useState} from "react";
import { withStyles } from "#material-ui/core/styles";
import { purple } from "#material-ui/core/colors";
import FormGroup from "#material-ui/core/FormGroup";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Switch from "#material-ui/core/Switch";
import Grid from "#material-ui/core/Grid";
import Table from "#material-ui/core/Table";
import TableBody from "#material-ui/core/TableBody";
import TableCell from "#material-ui/core/TableCell";
import TableContainer from "#material-ui/core/TableContainer";
import Typography from '#material-ui/core/Typography';
import TableHead from "#material-ui/core/TableHead";
import TableRow from "#material-ui/core/TableRow";
import Paper from "#material-ui/core/Paper";
import { makeStyles, withStyles } from "#material-ui/core/styles";
const AntSwitch = withStyles(theme => ({
root: {
width: 28,
height: 16,
padding: 0,
display: "flex"
},
switchBase: {
padding: 2,
color: theme.palette.grey[500],
"&$checked": {
transform: "translateX(12px)",
color: theme.palette.common.white,
"& + $track": {
opacity: 1,
backgroundColor: theme.palette.primary.main,
borderColor: theme.palette.primary.main
}
}
},
thumb: {
width: 12,
height: 12,
boxShadow: "none"
},
track: {
border: `1px solid ${theme.palette.grey[500]}`,
borderRadius: 16 / 2,
opacity: 1,
backgroundColor: theme.palette.common.white
},
checked: {}
}))(Switch);
const tableStyles = makeStyles({
table: {
minWidth: 150
}
});
export default function CustomizedSwitches() {
const [gridData, setGridData] = useState([
{ key: 6, operator: "OyPohjolanLiikenne Ab", checked: false },
{ key: 12, operator: "Helsingin Bussiliikenne Oy", checked: true },
{ key: 17, operator: "Tammelundin Liikenne Oy", checked: false },
{ key: 18, operator: "Pohjolan Kaupunkiliikenne Oy", checked: true },
{ key: 20, operator: "Bus Travel Ã…bergin Linja Oy", checked: false },
{ key: 21, operator: "Bus Travel Oy Reissu Ruoti", checked: true }
]);
const handleChange = (event, index) => {
gridData[index].checked = event.target.checked;
setGridData([...gridData]);
};
const tableClasses = tableStyles();
return (
<TableContainer component={Paper}>
<Table
stickyHeader
className={tableClasses.table}
size="small"
aria-label="a dense table"
>
<TableHead>
<TableRow>
<TableCell>Operator</TableCell>
{/* <TableCell align="right">Listed</TableCell> */}
<TableCell>Visible</TableCell>
<TableCell align="right">Total Placements</TableCell>
</TableRow>
</TableHead>
<TableBody>
{gridData.map( (row, index) => (
<TableRow key={row.key}>
<TableCell component="th" scope="row">
{row.operator}
</TableCell>
<TableCell>
<Typography component="div">
<Grid
component="label"
container
alignItems="center"
// justify="flex-end"
spacing={1}
>
<Grid item>Off</Grid>
<Grid item>
<AntSwitch
checked={row.checked}
onChange={(event) => handleChange(event, index)}
name="checkedC"
/>
</Grid>
<Grid item>On</Grid>
</Grid>
</Typography>
</TableCell>
<TableCell align="right">{"<<Placement value>>"}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}

Combining Arthur Ruben's answer and Drew Reese's comment.
Here's how I assigned props to a switch state array to be used by the switch and update fields inside the grid depending on the state of that switch.
Each row inside table needs its own state (and each column, as well, if you want to change columns within rows). You can monitor state of different switches as well as change fileds inside the row this way.
export default function TableComponent(props) {
const { rows } = props;
// generate array from incoming props,
// to be assigned to the state of this components
let stateInitArray = rows.map((row, index) => ({
id: index,
checked: false,
transactionType: "Session",
}));
const [gridState, setGridState] = React.useState(stateInitArray);
const handleSwitchChange = (event, index) => {
const { checked } = event.target;
let transactionType = checked ? "Payment" : "Session";
setGridState((prevState) =>
prevState.map((row, i) =>
// iterate over the state and update value of row where the switch was pressed
i === index ? { ...row, checked, transactionType } : row
)
);
};
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<TableCell align="left">isPayment?</TableCell>
<TableCell>Date</TableCell>
<TableCell align="left">Cost</TableCell>
<TableCell align="left">Transaction Type</TableCell>
<TableCell align="left">Column Gap</TableCell>
<TableCell align="left">Column Gap</TableCell>
<TableCell align="left">Column Gap</TableCell>
<TableCell align="left">Message</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, index) => (
<TableRow
key={row.name}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell>
<Switch
key={gridState[index].id}
checked={gridState[index].checked}
onChange={(event) => {
handleSwitchChange(event, index);
}}
inputProps={{ "aria-label": "controlled" }}
/>
</TableCell>
<TableCell component="th" scope="row">
{row.date}
</TableCell>
<TableCell align="left">
{gridState[index].transactionType === "Payment" ? "" : row.cost}
</TableCell>
<TableCell align="left">
{gridState[index].transactionType}
</TableCell>
<TableCell align="left"></TableCell>
<TableCell align="left"></TableCell>
<TableCell align="left"></TableCell>
<TableCell align="left">{row.message}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}

Related

How to center out pagination on MUI v5 table component and how to make a sticky column?

I'm working with MUI table components. I have made the below table example with pagination.
const MuiTable = () => {
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(5);
const [data, setData] = useState([]);
useEffect(() => {
setData(tableData);
}, []);
const emptyRows =
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - data.length) : 0;
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
return (
<TableContainer sx={{ maxHeight: "300px" }} component={Paper}>
<Table stickyHeader aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Id</TableCell>
<TableCell>First Name</TableCell>
<TableCell>Last Name</TableCell>
<TableCell align="center">Email</TableCell>
</TableRow>
</TableHead>
<TableBody>
{(rowsPerPage > 0
? data.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
: data
).map((row) => (
<TableRow
key={row.id}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell>{row.id}</TableCell>
<TableCell>{row.first_name}</TableCell>
<TableCell>{row.last_name}</TableCell>
<TableCell align="center">{row.email}</TableCell>
</TableRow>
))}
{emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
count={data.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
SelectProps={{
inputProps: {
"aria-label": "rows per page",
},
}}
labelDisplayedRows={({ page }) => {
return `Page: ${page}`;
}}
backIconButtonProps={{
color: "secondary",
}}
nextIconButtonProps={{ color: "secondary" }}
showFirstButton={true}
showLastButton={true}
labelRowsPerPage={<span>Rows:</span>}
sx={{
".MuiTablePagination-toolbar": {
backgroundColor: "rgba(100,100,100,0.5)",
},
".MuiTablePagination-selectLabel, .MuiTablePagination-input": {
fontWeight: "bold",
color: "blue",
},
}}
/>
</TableRow>
</TableFooter>
</Table>
</TableContainer>
);
};
I would like to know how I can make a sticky column (I would like to have the same effect as stickyHeader). On the other hand, I have tried to center out the pagination component by adding this on the TablePagination component but it is not centering the component:
sx={{
'.MuiTablePagination-root': {
display: 'flex',
justifyContent: 'center',
},
}}
I would appreciate any help on the regard.
sx={{ display: 'flex', justifyContent: 'center' }} works for me

React TypeError: Cannot read properties of undefined (reading 'state')

I want to update the text of a button when i click on that button. I am using setState for that but i am unable to use it as it gives me the following error:
TypeError: Cannot read properties of undefined (reading 'state')
Here is my code:
import Head from 'next/head';
import { Box, Container, Grid, Pagination } from '#mui/material';
import { products } from '../__mocks__/products';
import { ProductListToolbar } from '../components/product/product-list-toolbar';
import { ProductCard } from '../components/product/product-card';
import { DashboardLayout } from '../components/dashboard-layout';
import { CustomerListResults } from '../components/trip/trip-list-results';
import { customers } from '../__mocks__/customers';
import { trips } from '../__mocks__/trips';
import { TripListResults } from '../components/customer/customer-list-results';
import {
Avatar,
Card,
Checkbox,
Table,
TableBody,
TableCell,
TableHead,
TablePagination,
TableRow,
Typography,
Button
} from '#mui/material';
import NextLink from 'next/link';
const TripRequests = () => {
const accept =()=> {
this.setState({accept: 'Payment Pending'})
console.log("fsfdsa");
};
this.state = {
accept: "Accept"
};
return (
<>
<Head>
<title>
Trip Requests
</title>
</Head>
<Box
component="main"
sx={{
flexGrow: 1,
py: 8
}}
>
<Container maxWidth={false}>
<Box sx={{ mt: 3 }}>
{/* <CustomerListResults customers={trips} /> */}
<h2>Trip Requests (2)</h2>
</Box>
<Box sx={{ minWidth: 1050, mt: 3 }}>
<Table>
<TableHead>
<TableRow>
<TableCell padding="checkbox">
{/* <Checkbox
// checked={selectedCustomerIds.length === customers.length}
color="primary"
// indeterminate={
// selectedCustomerIds.length > 0
// && selectedCustomerIds.length < customers.length
// }
// onChange={handleSelectAll}
/> */}
</TableCell>
{/* <TableCell>
Trip Id
</TableCell> */}
<TableCell>
Customer
</TableCell>
<TableCell>
Departure
</TableCell>
<TableCell>
Destination
</TableCell>
<TableCell>
Truck / Driver
</TableCell>
<TableCell>
Action
</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow
hover
// key={customer.id}
// selected={selectedCustomerIds.indexOf(customer.id) !== -1}
>
<TableCell padding="checkbox">
{/* <Checkbox
checked={selectedCustomerIds.indexOf(customer.id) !== -1}
onChange={(event) => handleSelectOne(event, customer.id)}
value="true"
/> */}
</TableCell>
<TableCell>
Rohan Joshi
</TableCell>
<TableCell>
<Box
sx={{
alignItems: 'center',
display: 'flex'
}}
>
{/* <Avatar
src={customer.avatarUrl}
sx={{ mr: 2 }}
>
{getInitials(customer.name)}
</Avatar> */}
<Typography
color="textPrimary"
variant="body1"
>
A-50, Sec 67, Noida
</Typography>
</Box>
</TableCell>
<TableCell>
HUDA City Center
</TableCell>
<TableCell>
fds
</TableCell>
<TableCell>
<Button onClick={accept}>{state.accept}</Button>
<Button>Decline</Button>
</TableCell>
{/* <TableCell>
{format(customer.createdAt, 'dd/MM/yyyy')}
</TableCell> */}
</TableRow>
</TableBody>
</Table>
</Box>
</Container>
</Box>
</>
);
TripRequests.getLayout = (page) => (
<DashboardLayout>
{page}
</DashboardLayout>
)};
export default TripRequests;
Looks like you are mixing class-based components with function components. In your case this refers to the module-level this which is undefined. Function components don't make use of this.
To use state in function components you need to use the useState hook:
const TripRequests = () => {
const [acceptState, setAcceptState] = useState('Accept')
const accept = () => setAcceptState('Payment Pending')
// ...
}

Download file from a path

I am trying to create a download link in a React front end where the user will be clicking a button and a file will be downloaded.
To acheive this, I am trying to pass the file path inside a JS function and inside the function I want to call an API endpoint with that path, and the api will return me the file.
Now, for some reason, the code I am writing is not working. Kindly help me to rectify my mistake.
import React, { useState, useEffect } from "react";
import axios from "axios";
import moment from "moment";
import {
Grid,
Paper,
Typography,
Table,
TableCell,
TableContainer,
TableHead,
TableRow,
TableBody,
} from "#mui/material";
import Download from './Download';
import FileDownload from "js-file-download";
export default function Classroomtest() {
const uid = localStorage.getItem("uniqueid");
const [assignments, setassignments] = useState("");
useEffect(() => {
axios
.get(`http://localhost:5000/api/v1/app/get-my-assignments/${uid}`)
.then((res) => {
setassignments(res.data);
});
}, []);
const view = (path) =>{
//Nothing is coming here
}
return (
<Grid>
<Paper
sx={{
padding: 3,
margin: 1,
}}
>
<TableContainer
component={Paper}
sx={{
margin: "10px",
width: "97%",
}}
>
<Table sx={{ minWidth: 100 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell align="right">Topic</TableCell>
<TableCell align="right">Due</TableCell>
<TableCell align="right">Action</TableCell>
<TableCell align="right">Delete</TableCell>
</TableRow>
</TableHead>
{assignments?.data?.myassignments.map((item) => (
<TableBody key={item._id}>
<TableCell align="right">{item.assignmentname}</TableCell>
<TableCell align="right">{moment.utc(item.duedate).format("MMMM,DD")}</TableCell>
<TableCell align="right">
<button type="button" onClick={view(item.assignmentfilepath)}>Download</button>
</TableCell>
<TableCell align="right">Java</TableCell>
</TableBody>
))}
</Table>
</TableContainer>
</Paper>
</Grid>
);
}
Example of a file path returned is : uploads\user-1638470921415.pdf

How to update the value present inside array of array in reactjs

Working Code :https://codesandbox.io/s/broken-https-2i454?file=/src/App.js
I'm using Material UI in my reactjs project and I'm trying the update the value entered inside a textfield of a table using onChange function for the textfield, currently the value is present inside an array of array,I want to update the componentQuantity , I'm aware of passing the event to get the event.target.value , but what is the correct way to update the specific value of the textfield present inside a array of array. Please someone help me out here.
class TabData extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
data: [
{
BomComponentCode: "345543",
BomComponentName: "COMP1",
BomComponentRefUOM: "gm",
rowId: 0,
consumptionBatchNumbers: [
{
componentBatchNumber: "20",
componentQuantity: 2
},
{
componentBatchNumber: "21",
componentQuantity: 3
}
]
},
//2nd cloumn
{
BomComponentCode: "5543",
BomComponentName: "COMP2",
BomComponentRefUOM: "KG",
rowId: 1,
consumptionBatchNumbers: [
{
componentBatchNumber: "22",
componentQuantity: 4
},
{
componentBatchNumber: "23",
componentQuantity: 5
}
]
}
],
renderState: false
};
}
handleUpdate = (index, Code) => {
this.setState({
renderState: Code
});
};
handleChange = (e) => {
console.log(e.target.value)
};
render() {
const { classes } = this.props;
const { data, renderState } = this.state;
return (
<div className={classes.list}>
<React.Fragment>
<Grid
item
xs={12}
sm={12}
md={12}
className={classes.grid}
style={{ paddingLeft: "0" }}
>
<p variant="h6" className={classes.font}>
Table Details
</p>
<span className={classes.borders}></span>
</Grid>
<div>
<TableContainer component={Paper} className={classes.paper}>
<Table className={classes.table} aria-label="collapsible table">
<TableHead>
<TableRow>
<TableCell> Number</TableCell>
<TableCell> Text</TableCell>
<TableCell>UOM</TableCell>
<TableCell> Batch </TableCell>
<TableCell> Quantity</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
<React.Fragment>
{data.map((item, i) => (
<React.Fragment>
<TableRow key={i}>
<TableCell scope="row" align="left">
{item.BomComponentCode}
</TableCell>
<TableCell align="left">
{item.BomComponentName}
</TableCell>
<TableCell align="left">
{item.BomComponentRefUOM}
</TableCell>
<TableCell>
{item.consumptionBatchNumbers.map((row, i) => (
<div key={i}>
<TableCell align="left">
{row.componentBatchNumber}
</TableCell>
</div>
))}
</TableCell>
{renderState === item.BomComponentCode ? (
<TableCell align="left">
{item.consumptionBatchNumbers.map((row, indi) => (
<div key={indi}>
<input
// value={row.componentQuantity}
onChange={(e) =>
this.handleChange(e)
}
defaultValue={row.componentQuantity}
/>
</div>
))}
</TableCell>
) : (
<TableCell align="left">
{item.consumptionBatchNumbers.map((row, indi) => (
<div key={indi}>
<TableCell align="left">
{row.componentQuantity}
</TableCell>
</div>
))}
</TableCell>
)}
<TableCell>
<button
onClick={() =>
this.handleUpdate(i, item.BomComponentCode)
}
>
Update
</button>
</TableCell>
</TableRow>
</React.Fragment>
))}
</React.Fragment>
</TableBody>
</Table>
</TableContainer>
</div>
<Grid container justify="center">
<Grid
item
xs={4}
sm={4}
md={2}
style={{
textAlign: "center",
padding: "1rem"
}}
>
<button >Close</button>
</Grid>
</Grid>
</React.Fragment>
</div>
);
}
}
export default (TabData);
To modify the value in the deeply nested array you need indexes . Once you have the indexes its easier to change the values .
Change the handleChange method to accept the row and deeply nested row's indexes.
onChange={(e) => this.handleChange(e, i, indi)}
Once you have the indexes we can deep clone the original state to create a new copy of state and mutate it directly .
handleChange = (e, rowIndex, consumptionBatchIndex) => {
// deep clone the data
const clonedData = JSON.parse(JSON.stringify(this.state.data));
clonedData[rowIndex].consumptionBatchNumbers[
consumptionBatchIndex
].componentQuantity = e.target.value;
this.setState({
data: clonedData
});
};
Now we can read the value in the input as
<input
value={row.componentQuantity}
onChange={(e) => this.handleChange(e, i, indi)}
/>

Why is material-ui's tooltip not displaying when nested in a map function?

I am using the tooltip component taken from the #material-ui/core library. When trying to add a tooltip per cell (i.e. row), I cannot understand why my code is incorrect at producing a localized tooltip for each row. By "localized", I mean that each tooltip is unique to its row.
When using the approach in my code below, the tooltips will not be displayed as expected with the proper handler functions:
function Home({ /* ... */ }) {
const [ order, setOrder ] = useState('asc')
const [ orderBy, setOrderBy ] = useState('id')
const [ open, setOpen ] = useState(false)
const classes = useStyles()
const rows = []
return (
<TableBody>
{ stableSort(rows, getComparator(order, orderBy))
.map((row, index) => {
const labelId = `enhanced-table-checkbox-${index}`
return (
<TableRow>
<TableCell component="th" scope="row" padding="none">
<ClickAwayListener onClickAway={ handleTooltipClose }>
<Tooltip
arrow
PopperProps={ {
disablePortal: true,
} }
onClose={ handleTooltipClose }
open={ open }
disableFocusListener
disableHoverListener
disableTouchListener
title="Unique Tooltip per txn"
>
<div>
<button
onClick={ handleTooltipOpen }
className={ styles.tooltip }
>
<img
src='./eye-tooltip-button.png'
className={ styles.tooltipImg }
height='12px'
width='12px'
/>
</button>
</div>
</Tooltip>
</ClickAwayListener>
</TableCell>
<TableCell id={ labelId } align="right" size='small'>
{ row.id }
</TableCell>
<TableCell align="right" size='small'>{ row.description }</TableCell>
<TableCell align="right" size='small'>{ row.amount }</TableCell>
<TableCell align="right" size='small'>{ row.isCredit }</TableCell>
<TableCell
align="right"
size='small'
style={ {
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
maxWidth: '1px'
} }
>
{ row.date }
</TableCell>
<TableCell
align="right"
size='small'
style={ {
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
maxWidth: '1px'
} }
>
{ row.imageUrl }
</TableCell>
</TableRow>
)
}) }
</TableBody>
The expected behavior is:
have a button to the far left, at the start of each row,
onClick of a row's button,
a tooltip is displayed that contains more data for that row,
when you click away from the tooltip, it disappears.
Edit 1
I also turned this project into a Codesandbox so you all can play around with all of the code to hopefully figure out what's wrong.
Edit 2
I'm trying to make a table similar to Etherscan's table of transactions. So, I'm using it as a reference for what I want to achieve.
Related questions
Why is Tooltip not displaying?

Categories