0

I have created a grid that is accordian-style. You can click the arrow on the left to expand/reduce the number of rows. This grid actually works well in other places in the project with no issues. However, in this case, I am having an issue where the expand button in the first row is firing its onClick event whenever the grid is clicked. Anywhere in the grid, even on different rows, that one event is firing.

Please help me make sense of this.

I have tried adding in the onRowClick function, and stopping propagation based on that, with no success.

Here is my code:


import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';

import { withStyles } from '@material-ui/core/styles';
import TableCell from '@material-ui/core/TableCell';
import Paper from '@material-ui/core/Paper';
import { AutoSizer, Column, Table } from 'react-virtualized';
import IconButton from '@material-ui/core/IconButton';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import CheckIcon from '@material-ui/icons/Check';
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';

const styles = theme => ({
  flexContainer: {
    display: 'flex',
    alignItems: 'center',
    boxSizing: 'border-box',
  },
  tableRow: {
    cursor: 'pointer',
  },
  tableRowHover: {
    '&:hover': {
      backgroundColor: theme.palette.grey[200],
    },
  },
  tableCell: {
    flex: 1,
  },
  noClick: {
    cursor: 'initial',
  },
  button: {
    "&:hover": {
      backgroundColor: "transparent"
    }
  }
});


class MuiVirtualizedTable extends React.PureComponent {
  static defaultProps = {
    headerHeight: 48,
    rowHeight: 48,
  };

  getRowClassName = ({ index }) => {
    const { classes } = this.props;

    return clsx(classes.tableRow, classes.flexContainer, {
      [classes.tableRowHover]: false
    });
  };

  cellRenderer = (props) => {
    const { cellData, columnIndex } = props;
    const { columns, classes, rowHeight } = this.props;
    const style = props.rowData.child ? { height: rowHeight, backgroundColor: "#E8E8E8" } : { height: rowHeight, paddingLeft: 8, paddingRight: 4 }

    let isChild = props.rowData.child;
    let isExpanded = props.rowData.open;

    if (props.dataKey === "open") {
      return (
        <TableCell
          component="div"
          className={clsx(classes.tableCell, classes.flexContainer, {
            [classes.noClick]: false
          })}
          variant="body"
          style={style}
          align={(columnIndex != null && columns[columnIndex].numeric) || false ? 'right' : 'left'}
        >
          {
            isChild ?
              <div></div> :
              <IconButton onClick={(event) => this.props.expand(props.rowData)} className={classes.button}>
                {
                  isExpanded ? <KeyboardArrowDownIcon style={{ fontSize: 24 }} /> : <ChevronRightIcon style={{ fontSize: 24 }}></ChevronRightIcon>
                }
              </IconButton>
          }
        </TableCell>)
    }
    else if (props.dataKey === "select") {
      return (
        <TableCell
          component="div"
          className={clsx(classes.tableCell, classes.flexContainer, {
            [classes.noClick]: false
          })}
          variant="body"
          style={style}
          align={(columnIndex != null && columns[columnIndex].numeric) || false ? 'right' : 'left'}
        >
          {
            isChild ?
              <div></div> :
              <IconButton onClick={() => this.props.rowSelected(props.rowData)} className={classes.button}>
                <CheckIcon fontSize="inherit" style={{ color: "#D71920" }} />
              </IconButton>
          }
        </TableCell>)
    }

    else {
      return (
        <TableCell
          component="div"
          className={clsx(classes.tableCell, classes.flexContainer, {
            [classes.noClick]: true
          })}
          variant="body"
          style={style}
          align={(columnIndex != null && columns[columnIndex].numeric) || false ? 'right' : 'left'}
        >
          {cellData}
        </TableCell>
      );
    }
  };

  headerRenderer = ({ label, columnIndex }) => {
    const { headerHeight, columns, classes } = this.props;
    const textStyle = { color: "#D71920", fontSize: 14, fontWeight: 600 };

    return (
      <TableCell
        component="div"
        className={clsx(classes.tableCell, classes.flexContainer, classes.noClick)}
        variant="head"
        style={{ height: headerHeight, paddingLeft: 8 }}
        align={columns[columnIndex].numeric || false ? 'right' : 'left'}
      >
        <span style={textStyle}>{label}</span>
      </TableCell>
    );
  };

  render() {
    const { classes, columns, rowHeight, headerHeight, key, ...tableProps } = this.props;
    return (
      <AutoSizer>
        {({ height, width }) => (
          <Table
            key={key}
            height={height}
            width={width}
            rowHeight={rowHeight}
            headerHeight={headerHeight}
            {...tableProps}
            rowClassName={this.getRowClassName}
          >
            {columns.map(({ dataKey, ...other }, index) => {
              return (
                <Column
                  key={dataKey}
                  headerRenderer={headerProps =>
                    this.headerRenderer({
                      ...headerProps,
                      columnIndex: index,
                    })
                  }
                  className={classes.flexContainer}
                  cellRenderer={this.cellRenderer}
                  dataKey={dataKey}
                  {...other}
                />
              );
            })}
          </Table>
        )}
      </AutoSizer>
    );
  }
}

MuiVirtualizedTable.propTypes = {
  classes: PropTypes.object.isRequired,
  columns: PropTypes.arrayOf(
    PropTypes.shape({
      dataKey: PropTypes.string.isRequired,
      label: PropTypes.string.isRequired,
      numeric: PropTypes.bool,
      width: PropTypes.number.isRequired,
    }),
  ).isRequired,
  headerHeight: PropTypes.number,
  onRowClick: PropTypes.func,
  rowHeight: PropTypes.number,
};

const VirtualizedTable = withStyles(styles)(MuiVirtualizedTable);

// ---

export default function OrderGroupGrid(props) {
  const [rows, setRows] = React.useState({ data: [] });

  const expRow = (data) => {
    console.log("onSelectExpandRow: ", data)
    let row = rows.data.find(e => e.id === data.id);
    row.open = !row.open;

    let newRows = []

    if (row.open) {
      rows.data.forEach(r => {
        if (r.id !== row.id) {
          newRows.push(r);
        }
        else {
          newRows.push(r);
          r.items.forEach(child => {
            child.parentId = r.id
            child.child = true;
            newRows.push(child);
          })
        }
      })
    }
    else {
      newRows = rows.data.filter(r => r.parentId !== row.id);
    }

    setRows({ data: newRows });
  }

  const selectRow = (row) => {
    console.log("Selected Order Group: ", row)
    props.selectOrderGroup(row)
  }

  const rowClick = (e) => {
    console.log("rowClick - ", e.event);
    if (e && e.event && e.event.stopPropagation) {
      e.event.stopPropagation();
    }
  }

  if (rows.data.length === 0 && props.rows) {
    let newData = props.rows.map(r => {
      r.open = false;
      r.itemNumber = r.items.length + " items"
      return r;
    })
    setRows({ data: newData });
  }

  const columns = [
    { width: 40, maxWidth: 40, label: '', dataKey: 'open', flexGrow: 1 },
    { width: 200, label: 'Order Group Description', dataKey: 'description', flexGrow: 1 },
    { width: 100, label: 'Item Number', dataKey: 'itemNumber', flexGrow: 1 },
    { width: 200, label: 'Item Description', dataKey: 'itemDescription', flexGrow: 1 },
    { width: 60, label: '', dataKey: 'select', flexGrow: 1 }
  ]

  let rowCount = (rows && rows.data) ? rows.data.length : 0

  return (
    <React.Fragment>
      <Paper style={{ height: "35vh", width: '100%' }}>
        <VirtualizedTable
          rowCount={rowCount}
          rowGetter={({ index }) => rows.data[index]}
          columns={columns}
          expand={expRow}
          rowSelected={selectRow}
          onRowClick={rowClick}
        />
      </Paper>
    </React.Fragment>

  );
}

Versions:

"react": "^16.9.0" "react-virtualized": "^9.21.1"

Rayel
  • 1
  • 2

1 Answers1

0

Problem solved.

It was in the parent. I had made the mistake of placing this component in a Grid Container, instead of a Grid Item. The result of which (as far as I can tell), is that the first onClick event is being bound to the container. Which is why anytime I click in the grid (i.e. the container), the first (and only the first) row is opened or closed.

What a strange one.

Rayel
  • 1
  • 2