Single selection grid - reactjs

Hello I am trying to add selection logic to my material ui grid(table).
This is what I return,
return (
<Paper sx={{ width: "100%", overflow: "hidden" }}>
<Table style={{ width: "100%" }}>
<CatalogRightGridHeader selected={selected} />
<TableBody xs={12} style={{ width: "100%" }}>
{value.map((row) => {
return (
<TableRow hover role="checkbox" tabIndex={-1} key={row.nodeId}>
<TableCell style={{ width: "10px!important", padding: 2 }}>
<Checkbox/>
</TableCell>
{row.rowItems
.sort(
(row1, row2) => row1.column.sequence - row2.column.sequence
)
.map((column) => {
return column.column.dataType === "DATE" ? (
<TableCell
key={column.id}
align={column.align}
>
{format(
new Date(parseInt(column.value)),
dateFormat(),
new Date()
)}
</TableCell>
) : (
<TableCell
key={column.id}
align={column.align}
>
{column.value}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</Paper>
);
I want to have a logic on checkbox so whenever I check them I want to get checked row data and it should uncheck whenever I check another checkbox. I would appreciate if u could give me some tips how to do it or help me doing it.

You should take a look at MUI's DataGrid which comes with the checkbox selections.
Also, it sounds like you want these to act like radio buttons, which, the UX difference is that checkboxes are like multiple selects, whereas radio buttons infer that only one can be selected.
If you want to stay with a vanilla table, then adding in the checkbox like that is fine, from there, when you map, add in an index. The checkboxes need to be controlled
const [checked, setChecked] = React.useState(null)
const handleCheckChange = (event, index) => {
// If checkbox is changed to checked, set the index
if (event.target.checked) {
setChecked(index)
}
// Since on change is fired every time the state changes, it'll fire when it
// gets unchecked by checking something else, so only set back to null when
// it's unchecked while being the actively checked row
else if (!event.target.checked && index === checked) {
setChecked(null)
}
}
value.map((row, index) => {
return (
<TableRow hover role='checkbox' tabIndex={-1} key={row.nodeId}>
<TableCell style={{ width; '10px!important', padding: 2 }}>
<Checkbox
checked={checked === index}
onChange={(event) => handleCheckChange(event, index)}
/>
</TableCell>
...
)
}
From here, you now know the row that is checked and can pull that data as
rowData[checked]

Related

Each child in a list should have a unique "key" prop in table React

I don't know where is the error I'm getting in the console, everything seems fine, does anyone have any idea what could be wrong?
Unique key error
<TableBody>
{announcements.map((announcement) => (
<TableRow
key={announcement.id}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell align="left">
<Box display='flex' alignItems='center'>
<Avatar src={announcement.photoUrl} alt={announcement.announcementTitle}
style={{ height: 50, width: 50, marginRight: 20 }} />
<span>{announcement.announcementTitle}</span>
</Box>
</TableCell>
<TableCell align="right">
<Button onClick={() => handleSelectAnnouncement(announcement)} startIcon={<Edit />} />
<Button startIcon={<Delete />} color='error' />
</TableCell>
</TableRow>
))}
</TableBody>
After changing to key={index} I get this error, but I still don't know what is wrong. There are only 6 ID fields in the database, and it can't be duplicated anywhere.
warning after update key
Below is a link to the repository because there is quite a lot of code.
Last commit on GitHub
Most probably here you have a duplicate announcement.id values you can fix it like this use the index as key it will be always unique :
{announcements.map((announcement,index) => (
<TableRow
key={index}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }} > ... </TableRow>
You are likely passing some undefined or null value to the key prop. Check whether every announcement object has an string id property.
However, it's not recommended to use the array index as unique key when the array comes from a database (or will change at some point).

New Dataset not Reflecting Table Display

I am able to print the updated value of dataset but the new added row did not reflecting the updated data from the table component display.
I temporarily put the event on keypress to trigger for adding new row on the table, here the simple code that I playing.
function createData(hardware, cost, status) {
return { hardware, cost, status };
}
const tblData = [];
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
tblData: [],
};
}
render() {
return (
<><><Box
sx={{
width: 500,
maxWidth: '100%',
}}
>
<TextField
fullWidth
label="Scan Here"
id="fullWidth"
onKeyPress={(e) => {
if (e.key === 'Enter') {
//*****--------------------------------------------------
// - Here is the part where I was able to get the updated data but not reflecting on the table display
console.log('Enter key pressed');
tblData.push(createData('blah', 356, 'Yes'));
console.log(tblData);
//
//--------------------------------------------------*****
}
} } />
</Box><TableContainer component={Paper}>
<Table sx={{ minWidth: 650, marginTop: '10px' }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Hardware</TableCell>
<TableCell align="right">Cost</TableCell>
<TableCell align="right">status</TableCell>
</TableRow>
</TableHead>
<TableBody>
{tblData.map((row) => (
<TableRow
key={row.name}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">
{row.hardware}
</TableCell>
<TableCell align="right">{row.cost}</TableCell>
<TableCell align="right">{row.status}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer></>
)
}
}
export default Main;
Any suggestion or comments how to refresh the updated data. TIA
Just pushing element to the state doesn't trigger rerendering.
<TextField
fullWidth
label="Scan Here"
id="fullWidth"
onKeyPress={(e) => {
console.log('Enter key pressed');
this.setState((prev) => {
tblData: prev.tblData.concat([createData('blah', 356, 'Yes')]) // this line is edited.
}))
}}
/>
Hope this would be helpful for you.
It's working now, I just missed the setState to refresh the value.
console.log('Enter key pressed');
tblData.push(createData('blah', 356, 'Yes'));
this.setState({
setState: tblData
});

sortDirection prop of TableCell doesn't work

I have a table with items. I use cookies to save them. Also, I have two buttons (increase and decrease). When I press them to change qty it works, but it also puts the item's row to the bottom of the list. I need to keep them in the same position where they are. I used sortDirection prop to TableCell component to make an order, but it also didn't work. Please help update the code so I can keep items in the same position.
Thanks in advance.
export default function CartItemsTable() {
const [cookies, setCookie, removeCookie] = useCookies();
function IncreaseQTY(article) {
var newCookie = cookies[article];
newCookie.qty++;
setCookie(article, newCookie, {
expires: new Date(Date.now() + 604800000),
});
}
function DecreaseQTY(article) {
var newCookie = cookies[article];
newCookie.qty--;
if (newCookie.qty === 0) {
removeCookie(article);
} else {
setCookie(article, newCookie, {
expires: new Date(Date.now() + 604800000),
});
}
}
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} >
<TableHead>
<TableRow>
<TableCell >Name</TableCell>
<TableCell sortDirection="asc" align="center">Code</TableCell>
<TableCell align="center">Price</TableCell>
<TableCell align="center">QTY</TableCell>
<TableCell align="center">Total</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Object.keys(cookies).map(function (key, index) {
return (
<TableRow
key={index}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell component="th" scope="row">
{cookies[key].name}
</TableCell>
<TableCell align="center">{cookies[key].article}</TableCell>
<TableCell align="center">{cookies[key].price}</TableCell>
<TableCell align="center">
<ButtonGroup
variant="contained"
aria-label="outlined primary button group"
>
<Button
onClick={() => {
DecreaseQTY(cookies[key].article);
}}
>
-
</Button>
<Button variant="text" disableRipple={true}>
{cookies[key].qty}
</Button>
<Button
onClick={() => {
IncreaseQTY(cookies[key].article);
}}
>
+
</Button>
</ButtonGroup>
</TableCell>
<TableCell align="center">
{cookies[key].qty * cookies[key].price}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
);
}
Before I press the button
After I press the increase button, first line went to the bottom
Update
I guess this problem can be occurred because of the cookies order or even related to react-cookie package, so I added console.log(cookies); to IncreaseQTY function. After a couple increasing it starts to show this way

I am trying to filter out data but not able to display them properly

I am using MUI to display a dummy data from a json file onto the table.
I have an input field which onChange event would call the function to filter out data based on input and show on the table. The filter of the data works and is displayed on the table as the last row but the old data is also shown as well. I have tried different ways but still could not figure out what is the issue.
const [sdata, setsData] = useState(Data); // data from file
const [searched, setSearched] = useState(""); // maintain state of the search field
const [filteredResults, setFilterdResults] = useState([]) // maintain state of filtered data
// function to filter data...
const requestSearch = (value) => {
let filteredData = sdata.filter((val) => {
if (searched === '') {
return val
} else {
return val.student_name.toLowerCase().includes(value.toLowerCase())
}
})
setFilterdResults(filteredData)
console.log("Filtered Data", filteredData)
}
// search input filed **only one for now**
<div className="search">
<Box
sx={{ display: "flex", alignItems: "flex-end", paddingRight: "10px" }}
>
<SearchIcon sx={{ color: "action.active", mr: 1, my: 0.5 }} />
<TextField
id="student"
label="Student"
variant="standard"
value={searched}
onChange={(evt) => {
setSearched(evt.target.value)
requestSearch(evt.target.value);
// console.log(evt.target.value);
}}
/>
<SearchIcon sx={{ color: "action.active", mr: 1, my: 0.5 }} />
<TextField id="zone" label="Zone" variant="standard" />
</Box>
</div>
// displaying data based on condition (if there is a searched input or not)
<TableContainer>
<Table aria-label="studentInfo">
<TableHead>
<TableRow>
{columns.map((col) => (
<TableCell
sx={{
backgroundColor: "#662d91",
color: "white",
fontSize: matches ? "12" : "18px",
fontWeight: "bold",
}}
key={col.id}
align={col.align}
style={{
minWidth: matches ? col.minWidth : col.mobWidth,
paddingLeft: "5px",
}}
>
{col.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{searched.length = 1 ? (
<>
{
filteredResults.map((std) => (
<StyledTableRow
// style={{ backgroundColor: getZoneColor(std.zone) }}
key={std.tg}
>
<StyledTableCell>{std.tg}</StyledTableCell>
<StyledTableCell>{std.student_name}</StyledTableCell>
<StyledTableCell>{std.admin_no}</StyledTableCell>
<StyledTableCell>{std.action}</StyledTableCell>
<StyledTableCell
style={{
color:
// "black",
getZoneColor(std.zone),
}}
>
{std.zone}
</StyledTableCell>
</StyledTableRow>
))
}
</>
) : (
<>
{
sdata.map((std) => (
<StyledTableRow
// style={{ backgroundColor: getZoneColor(std.zone) }}
key={std.tg}
>
<StyledTableCell>{std.tg}</StyledTableCell>
<StyledTableCell>{std.student_name}</StyledTableCell>
<StyledTableCell>{std.admin_no}</StyledTableCell>
<StyledTableCell>{std.action}</StyledTableCell>
<StyledTableCell
style={{
color:
// "black",
getZoneColor(std.zone),
}}
>
{std.zone}
</StyledTableCell>
</StyledTableRow>
))
}
</>
)}
</TableBody>
</Table>
</TableContainer>
The weird thing is if I force reload the tab and do the search it runs flawlessly but the next time I search it causes the issue
After force reload first search
The next search
try code below
const filterData = (inputValue) => {
const filteredData = sdata.filter(i => i.student_name?.includes(inputValue))
setsetSearched(inputValue);
setFilterdResults(filteredData)
}
call the function on onChange property of your input
<input onChange={(e)=> filterData(e.target.value)} />
and change your condition to this
searched.length > 0

React using state vs using local variable, state works but local variable does not

I tried to create a function to retrieve all the users in a database, however, if I tried to just use the local variable like in the image, it does not work at all. If I tried to useState (the commented code), it works when I think there should be no difference between them. The users list is being used for the map function in the TableBody part.
const RegisteredUsers = () => {
//const [customers, setCustomers] = useState([]);
let customers = [];
useEffect(async () => {
await userService.getAll().then(customersList =>{
//setCustomers(customersList);
customers = customersList;
}).catch((err)=> {
console.log("Error: ", err);
});
});
return (
<Box
sx={{
backgroundColor: "background.default",
p: 3,
}}
>
<Card>
<Divider/>
<Box
sx={{
alignItems: "center",
display: "flex",
flexWrap: "wrap",
m: -1,
p: 2,
}}
>
<Box
sx={{
m: 1,
maxWidth: "100%",
width: 500,
}}
>
<TextField
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon fontSize="small"/>
</InputAdornment>
),
}}
placeholder="Search customers"
variant="outlined"
/>
</Box>
<Box
sx={{
m: 1,
width: 240,
}}
>
<TextField
label="Sort By"
name="sort"
select
SelectProps={{native: true}}
variant="outlined"
>
{sortOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</TextField>
</Box>
</Box>
<Scrollbar>
<Box sx={{minWidth: 700}}>
<Table>
<TableHead>
<TableRow>
<TableCell padding="checkbox">
<Checkbox color="primary"/>
</TableCell>
<TableCell>Name</TableCell>
<TableCell>Email</TableCell>
<TableCell>Role</TableCell>
<TableCell>Company Id</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{customers.map((customer) => (
<TableRow hover key={customer.id}>
<TableCell padding="checkbox">
<Checkbox color="primary"/>
</TableCell>
<TableCell>
<Box
sx={{
alignItems: "center",
display: "flex",
}}
>
<Avatar
alt={customer.client_name}
sx={{
height: 42,
width: 42,
}}
/>
<Box sx={{ml: 1}}>
<Link color="inherit" variant="subtitle2">
{customer.client_name}
</Link>
</Box>
</Box>
</TableCell>
<TableCell>
<Typography color="textSecondary" variant="body2">
{customer.email}
</Typography>
</TableCell>
<TableCell>{customer.role}</TableCell>
<TableCell>{customer.company_id}</TableCell>
<TableCell align="right">
<IconButton>
<PencilAltIcon fontSize="small"/>
</IconButton>
<IconButton>
<ArrowRightIcon fontSize="small"/>
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Box>
</Scrollbar>
<TablePagination
component="div"
count={customers.length}
onPageChange={() => {
}}
onRowsPerPageChange={() => {
}}
page={0}
rowsPerPage={5}
rowsPerPageOptions={[5, 10, 25]}
/>
<Comments/>
</Card>
</Box>
)
};
This is my userService.getAll()
export const userService = {
getAll,
getById
};
async function getAll() {
let customers = [];
await axios.get("http://localhost:3000/api/v1/users", {
headers: authHeader()
}).then(customersList => {
customers.push(...customersList.data.users);
}).catch((err)=> {
console.log("Error: ", err);
})
return customers;
}
I tried many methods including using async function since I thought that it could be the case that React renders before value assigned to customers, but it does not work as well.
I am new to React so any helps would be great! Thanks.
This is precisely why you need a state.
In your first attempt, the component has rendered before you assign a new value to customers and won't re-render.
I thought that it could be the case that React renders before value assigned to customers
Precisely as you guessed, you have two ways to instruct a component to re-render:
change the component state (as in the hook)
change the component props
One of these two option is required to instruct react to recompute the component
The key hint is that, useState will preserve its value through rerenders and changing state will trigger rerender. It means that if you store something in normal variable after render, it will not show up in your DOM because there is still value from previous render. And if you trigger new render, your variable is redeclared with empty array and you are where you have been before fetch.
At runtime local variable value will be called synchronously (no idea when it will be populated with data ), add following check to your map to make sure it runs when the value is there:
sortOptions && sortOptions?.map (...)
Also, as you are getting the values from API, react recomment using state with useState.

Resources