简体   繁体   中英

Material-UI collapse table with hooks

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 thisCodeSandbox 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...

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;
      }
    })
  );
};

编辑 agitated-browser-wy3b5

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM