Reputation: 586
I've been trying to come around this issue with no luck, I need help with my handleClick() function where it collapses and expands all table rows at the same time as shown in this CodeSandbox snippet. Any idea why is it behaving like so ? and how to fix it ?
2- Also when component mounts, it fires multiple times.
import React, { useEffect, useState } from "react";
import {
Box,
Collapse,
IconButton,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
Paper
} from "@material-ui/core";
import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";
import { v4 as uuid } from "uuid";
function CollapsibleTable() {
const [fetchedData, setFetchedData] = useState([]);
const [isLoding, setIsLoding] = useState(false);
const [expanded, setExpanded] = useState();
const getData = async () => {
const response = await fetch("https://reqres.in/api/users?page=2");
const result = await response.json();
setIsLoding(false);
setFetchedData(
result.data.map((user) => ({
user_data: {
id: user.id,
firstName: user.first_name,
lastName: user.last_name,
email: user.email
},
other_data: {
avatar: user.avatar
}
}))
);
};
const handleClick = () => {
setExpanded(!expanded);
};
useEffect(() => {
getData();
}, []);
return (
<TableContainer component={Paper}>
<Table size="small" aria-label="ccg">
<TableHead>
<TableRow>
<TableCell />
<TableCell>Index</TableCell>
<TableCell>ID</TableCell>
<TableCell>First Name</TableCell>
<TableCell>Last Name</TableCell>
<TableCell align="right">Email</TableCell>
</TableRow>
</TableHead>
<TableBody>
{isLoding
? "Loading . . ."
: fetchedData.map((user, index) => (
<React.Fragment key={uuid()}>
<TableRow
style={{
borderBottom: "unset"
}}
>
<TableCell>
<IconButton
aria-label="expand row"
size="small"
onClick={handleClick}
>
{expanded ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</TableCell>
<TableCell component="th" scope="row">
{index}
</TableCell>
<TableCell component="th" scope="row">
{user.user_data.id}
</TableCell>
<TableCell component="th" scope="row">
{user.user_data.firstName}
</TableCell>
<TableCell component="th" scope="row">
{user.user_data.lastName}
</TableCell>
<TableCell align="right">{user.user_data.email}</TableCell>
</TableRow>
<TableRow>
<TableCell
style={{ paddingBottom: 0, paddingTop: 0 }}
colSpan={6}
>
<Collapse
in={expanded}
timeout="auto"
unmountOnExit={true}
>
<Box margin={1}>
<Typography variant="h6" gutterBottom component="div">
Txns History
</Typography>
<Table size="small" aria-label="purchases">
<TableHead>
<TableRow>
<TableCell>Avatar</TableCell>
<TableCell>Avatar</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow key={uuid()}>
<TableCell>
<img
style={{
borderRadius: "50%",
width: "50px"
}}
src={user.other_data.avatar}
alt="avatar"
/>
</TableCell>
<TableCell>
<img
style={{
borderRadius: "50%",
width: "50px"
}}
src={user.other_data.avatar}
alt="avatar"
/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</Box>
</Collapse>
</TableCell>
</TableRow>
</React.Fragment>
))}
</TableBody>
</Table>
</TableContainer>
);
}
export default CollapsibleTable;
I've tried to toggle each one by it's index value like so
const handleClick = (index) => {
setExpanded({
...expanded,
[index]: !expanded[index]
});
}
<Collapse in={expanded[index]} timeout="auto" unmountOnExit={true}>
//...
</Collapse>
But still doesn't work as expected...
Upvotes: 1
Views: 2087
Reputation: 14201
This is because your expanded
state is the state for all of the Collapse
components.
This is what your component tree currently looks like after mapping:
<>
<Collapse
in={expanded}
/>
<Collapse
in={expanded}
/>
<Collapse
in={expanded}
/>
</>
So when the expanded
state changes to true, then they are all expanded.
To solve this, you could have a different state for each of the Collapse
components. In my example below, I used an array to store these boolean values. Such that if array element of index X is true, then collapse X should be expanded
const getData = async () => {
...
// after you receive your data, initialize all expanded boolean to be false so they are all closed by default
setExpanded([...Array(result.data.length)].map((val) => false));
};
<Collapse
in={expanded[index]} // we use index to reference the correlated expanded state value
timeout="auto"
unmountOnExit={true}
>
<IconButton
aria-label="expand row"
size="small"
onClick={() => handleClick(index)} // onClick pass the index clicked
>
// set new state depending on what Collapse index was clicked
const handleClick = (index) => {
setExpanded(
expanded.map((boolean_value, i) => {
if (index === i) {
// once we retrieve the collapse index, we negate it
return !boolean_value;
} else {
// all other collapse will be closed
return false;
}
})
);
};
Upvotes: 1