Matt
Matt

Reputation: 8942

Last rows in inner table don't expand in Fixed Data Table

I use expanded rows in fixed-data-table-2 component. I have 3 levels on inner tables:

enter image description here

If I click collapse cells in inner table (second nested level), rows don't expand and last nested table is not rendered. It occurs after first click of parent row, but after second click the table is rendered.

What is more strange, this behaviour doesn't happen if a) I click first three rows of second table or b) if I expand first row in main(first) table

It happens with last rows of second table if other than first row of main table is expanded.

You can see this behaviour in this and this recording.

codesandbox

CollapseCell.jsx

import React from 'react';
import { Cell } from 'fixed-data-table-2';

const { StyleSheet, css } = require('aphrodite');

export default class CollapseCell extends React.PureComponent {
  render() {
    const {
      data, rowIndex, columnKey, collapsedRows, callback, ...props
    } = this.props;
    return (
      <Cell {...props} className={css(styles.collapseCell)}>
        <a onClick={evt => callback(rowIndex)}>{collapsedRows.has(rowIndex) ? '\u25BC' : '\u25BA'}</a>
      </Cell>
    );
  }
}

const styles = StyleSheet.create({
  collapseCell: {
    cursor: 'pointer',
  },
});

TestMeet.jsx

import React, { Component } from 'react';
import debounce from 'lodash/debounce';
import { Table, Column, Cell } from 'fixed-data-table-2';
import isEmpty from 'lodash/isEmpty';
import 'fixed-data-table-2/dist/fixed-data-table.min.css';
import CollapseCell from './CollapseCell.jsx';
import SecondInnerTable from './SecondInnerTable';

const { StyleSheet, css } = require('aphrodite');

export default class TestMeetView extends Component {
  static propTypes = {};

  state = {
    tableData: [
      {
        start: '5/19',
        end: '5/20',
        host: 'DACA',
      },
      {
        start: '6/15',
        end: '6/15',
        host: 'DACA',
      },
      {
        start: '6/16',
        end: '6/17',
        host: 'DACA',
      },
      {
        start: '7/15',
        end: '7/16',
        host: 'DACA',
      },
      {
        start: '7/30',
        end: '7/31',
        host: 'DACA',
      },
    ],
    columnWidths: {
      start: 200,
      end: 200,
      host: 200,
      action: 100,
    },
    tableContainerWidth: 0,
    numOfExpRows: 0,
    expChildRows: {},
    collapsedRows: new Set(),
    scrollToRow: null,
  };

  componentDidMount() {
    this.updateWidth();
    this.updateWidth = debounce(this.updateWidth, 200);
    window.addEventListener('resize', this.updateWidth);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.updateWidth);
  }

  onTableColumnResizeEndCallback = (newColumnWidth, columnKey) => {
    this.setState(({ columnWidths }) => ({
      columnWidths: {
        ...columnWidths,
        [columnKey]: newColumnWidth,
      },
    }));
  };

  updateWidth = () => {
    if (this.tableContainer.offsetWidth === this.state.tableContainerWidth) {
      return;
    }

    if (
      this.tableContainer &&
      this.tableContainer.offsetWidth !== this.state.tableContainerWidth
    ) {
      const newTableContainerWidth = this.tableContainer.offsetWidth;
      this.setState({
        tableContainerWidth: newTableContainerWidth,
        columnWidths: {
          start: newTableContainerWidth / 3,
          end: newTableContainerWidth / 3,
          host: newTableContainerWidth / 3,
        },
      });
    }
  };

  handleCollapseClick = (rowIndex) => {
    const { collapsedRows } = this.state;
    const shallowCopyOfCollapsedRows = new Set([...collapsedRows]);
    let scrollToRow = rowIndex;
    if (shallowCopyOfCollapsedRows.has(rowIndex)) {
      shallowCopyOfCollapsedRows.delete(rowIndex);
      scrollToRow = null;
    } else {
      shallowCopyOfCollapsedRows.add(rowIndex);
    }

    let numOfExpRows = 0;
    if (collapsedRows.size > 0) {
      numOfExpRows = collapsedRows.size;
    }

    let resetExpChildRow = -1;
    if (collapsedRows.has(rowIndex)) {
      numOfExpRows -= 1;
      resetExpChildRow = rowIndex;
    } else {
      numOfExpRows += 1;
    }

    if (resetExpChildRow === -1) {
      this.setState({
        numOfExpRows,
        scrollToRow,
        collapsedRows: shallowCopyOfCollapsedRows,
      });
    } else {
      this.setState({
        numOfExpRows,
        scrollToRow,
        collapsedRows: shallowCopyOfCollapsedRows,
        expChildRows: {
          ...this.state.expChildRows,
          [rowIndex]: 0,
        },
      });
    }
  };

  subRowHeightGetter = (index) => {
    const numExpChildRows = this.state.expChildRows[index] ? this.state.expChildRows[index] : 0;
    return this.state.collapsedRows.has(index) ? 242 * (numExpChildRows + 1) + 50 : 0;
  };

  rowExpandedGetter = ({ rowIndex, width, height }) => {
    if (!this.state.collapsedRows.has(rowIndex)) {
      return null;
    }

    const style = {
      height,
      width: width - 10,
    };

    return (
      <div style={style}>
        <div className={css(styles.expandStyles)}>
          <SecondInnerTable
            changeNumOfExpandedRows={this.setNumOfInnerExpandedRows}
            parentRowIndex={rowIndex}
          />
        </div>
      </div>
    );
  };

  setNumOfInnerExpandedRows = (numOfExpandedRows, rowIndex) => {
    this.setState({
      expChildRows: {
        ...this.state.expChildRows,
        [rowIndex]: numOfExpandedRows,
      },
    });
  };

  render() {
    let sumOfExpChildRows = 0;
    if (!isEmpty(this.state.expChildRows)) {
      sumOfExpChildRows = Object.values(this.state.expChildRows).reduce((a, b) => a + b);
    }
    return (
      <div className="test-view">
        <div className="container-fluid">
          <div className="mb-5" ref={el => (this.tableContainer = el)}>
            <Table
              scrollToRow={this.state.scrollToRow}
              rowsCount={this.state.tableData.length}
              rowHeight={40}
              headerHeight={40}
              width={this.state.tableContainerWidth}
              height={(this.state.numOfExpRows + sumOfExpChildRows + 1) * 242}
              subRowHeightGetter={this.subRowHeightGetter}
              rowExpanded={this.rowExpandedGetter}
              touchScrollEnabled
              onColumnResizeEndCallback={this.onTableColumnResizeEndCallback}
              isColumnResizing={false}
            >
              <Column
                cell={<CollapseCell callback={this.handleCollapseClick} collapsedRows={this.state.collapsedRows} />}
                fixed
                width={30}
              />
              <Column
                columnKey="start"
                header={<Cell>Start</Cell>}
                cell={props => <Cell {...props}>{this.state.tableData[props.rowIndex].start}</Cell>}
                width={this.state.columnWidths.start}
                isResizable
              />
              <Column
                columnKey="end"
                header={<Cell>End</Cell>}
                cell={props => <Cell {...props}>{this.state.tableData[props.rowIndex].end}</Cell>}
                width={this.state.columnWidths.end}
                isResizable
              />
              <Column
                columnKey="host"
                header={<Cell>Host</Cell>}
                cell={props => <Cell {...props}>{this.state.tableData[props.rowIndex].host}</Cell>}
                width={this.state.columnWidths.host}
                isResizable
              />
            </Table>
          </div>
        </div>
      </div>
    );
  }
}

const styles = StyleSheet.create({
  expandStyles: {
    height: '242px',
    margin: '10px',
  },
});

SecondInnerTable.jsx

import React, { Component } from 'react';
import { Table, Column, Cell } from 'fixed-data-table-2';
import debounce from 'lodash/debounce';
import 'fixed-data-table-2/dist/fixed-data-table.min.css';
import CollapseCell from './CollapseCell';
import ThirdInnerTable from './ThirdInnerTable';

const { StyleSheet, css } = require('aphrodite');

export default class SecondInnerTable extends Component {
  state = {
    tableData: [
      {
        dateOfSession: '5/19/18',
        timeline: '4h00m/4h30m',
        entries: '400/900',
      },
      {
        dateOfSession: '5/19/18',
        timeline: '4h00m/4h30m',
        entries: '400/900',
      },
      {
        dateOfSession: '5/19/18',
        timeline: '4h00m/4h30m',
        entries: '400/900',
      },
      {
        dateOfSession: '5/19/18',
        timeline: '4h00m/4h30m',
        entries: '400/900',
      },
      {
        dateOfSession: '5/19/18',
        timeline: '4h00m/4h30m',
        entries: '400/900',
      },
    ],
    tableColumns: {
      dateOfSession: { label: 'Date of Session', width: 150 },
      timeline: { label: 'Timeline', width: 150 },
      entries: { label: 'Entries', width: 150 },
    },
    tableContainerWidth: 0,
    tableContainerHeight: 252,
    collapsedRows: new Set(),
    scrollToRow: null,
  };

  componentDidMount() {
    this.updateWidth();
    this.updateWidth = debounce(this.updateWidth, 200);
    window.addEventListener('resize', this.updateWidth);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.updateWidth);
  }

  onSessionsTableColumnResizeEndCallback = (newColumnWidth, columnKey) => {
    this.setState(({ tableColumns }) => ({
      tableColumns: {
        ...tableColumns,
        [columnKey]: { label: tableColumns[columnKey].label, width: newColumnWidth },
      },
    }));
  };

  updateWidth = () => {
    if (this.tableContainer.offsetWidth === this.state.tableContainerWidth) {
      return;
    }
    if (
      this.tableContainer &&
      this.tableContainer.offsetWidth !== this.state.tableContainerWidth
    ) {
      const newTableContainerWidth = this.tableContainer.offsetWidth - 20;
      const newColumnsWidth = newTableContainerWidth / 3;
      this.setState(({ tableColumns }) => ({
        tableContainerWidth: newTableContainerWidth,
        tableColumns: {
          dateOfSession: { label: tableColumns.dateOfSession.label, width: newColumnsWidth },
          timeline: { label: tableColumns.timeline.label, width: newColumnsWidth },
          entries: { label: tableColumns.entries.label, width: newColumnsWidth },
        },
      }));
    }
  };

  handleCollapseClick = (rowIndex) => {
    const { collapsedRows } = this.state;
    const shallowCopyOfCollapsedRows = new Set([...collapsedRows]);
    let scrollToRow = rowIndex;
    if (shallowCopyOfCollapsedRows.has(rowIndex)) {
      shallowCopyOfCollapsedRows.delete(rowIndex);
      scrollToRow = null;
    } else {
      shallowCopyOfCollapsedRows.add(rowIndex);
    }

    let numOfExpRows = 0;
    if (collapsedRows.size > 0) {
      numOfExpRows = collapsedRows.size;
    }

    if (collapsedRows.has(rowIndex)) {
      numOfExpRows -= 1;
    } else {
      numOfExpRows += 1;
    }

    this.setState(
      {
        tableContainerHeight: 252 * (numOfExpRows + 1),
        scrollToRow,
        collapsedRows: shallowCopyOfCollapsedRows,
      },
      () => {
        this.props.changeNumOfExpandedRows(numOfExpRows, this.props.parentRowIndex);
      },
    );
  };

  subRowHeightGetter = index => (this.state.collapsedRows.has(index) ? 272 : 0);

  rowExpandedGetter = ({ rowIndex, width, height }) => {
    if (!this.state.collapsedRows.has(rowIndex)) {
      return null;
    }

    const style = {
      height,
      width: width - 10,
    };
    return (
      <div style={style}>
        <div className={css(styles.expandStyles)}>
          <ThirdInnerTable parentRowIndex={rowIndex} />
        </div>
      </div>
    );
  };

  render() {
    return (
      <div className="mb-2" ref={el => (this.tableContainer = el)}>
        <Table
          scrollToRow={this.state.scrollToRow}
          rowsCount={this.state.tableData.length}
          rowHeight={40}
          headerHeight={50}
          width={this.state.tableContainerWidth}
          height={this.state.tableContainerHeight}
          subRowHeightGetter={this.subRowHeightGetter}
          rowExpanded={this.rowExpandedGetter}
          touchScrollEnabled
          onColumnResizeEndCallback={this.onSessionsTableColumnResizeEndCallback}
          isColumnResizing={false}
        >
          <Column
            cell={<CollapseCell callback={this.handleCollapseClick} collapsedRows={this.state.collapsedRows} />}
            fixed
            width={30}
          />
          {Object.keys(this.state.tableColumns).map(key => (
            <Column
              key={key}
              columnKey={key}
              header={<Cell>{this.state.tableColumns[key].label}</Cell>}
              cell={props => <Cell {...props}>{this.state.tableData[props.rowIndex][key]}</Cell>}
              width={this.state.tableColumns[key].width}
              isResizable
            />
          ))}
        </Table>
      </div>
    );
  }
}

const styles = StyleSheet.create({
  expandStyles: {
    height: '252px',
    margin: '10px',
  },
});

ThirdInnerTable.jsx

import React, { Component } from 'react';
import debounce from 'lodash/debounce';
import { Table, Column, Cell } from 'fixed-data-table-2';
import 'fixed-data-table-2/dist/fixed-data-table.min.css';

export default class ThirdInnerTable extends Component {
  state = {
    tableData: [
      {
        eventNumber: '1',
        qualifyingTime: 'N/A',
        selected: 'N/A',
      },
      {
        eventNumber: '1',
        qualifyingTime: 'N/A',
        selected: 'N/A',
      },
      {
        eventNumber: '1',
        qualifyingTime: 'N/A',
        selected: 'N/A',
      },
      {
        eventNumber: '1',
        qualifyingTime: 'N/A',
        selected: 'N/A',
      },
      {
        eventNumber: '1',
        qualifyingTime: 'N/A',
        selected: 'N/A',
      },
    ],
    tableColumns: {
      eventNumber: { label: 'Event number', width: 150 },
      qualifyingTime: { label: 'Qualifying time', width: 150 },
      selected: { label: 'Selected?', width: 150 },
    },
    tableContainerWidth: 0,
    numOfColumns: 3,
  };

  componentDidMount() {
    this.updateWidth();
    this.updateWidth = debounce(this.updateWidth, 200);
    window.addEventListener('resize', this.updateWidth);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.updateWidth);
  }

  onEventsTableColumnResizeEndCallback = (newColumnWidth, columnKey) => {
    this.setState(({ tableColumns }) => ({
      tableColumns: {
        ...tableColumns,
        [columnKey]: { label: tableColumns[columnKey].label, width: newColumnWidth },
      },
    }));
  };

  updateWidth = () => {
    if (this.tableContainer.offsetWidth === this.state.tableContainerWidth) {
      return;
    }
    if (
      this.tableContainer &&
      this.tableContainer.offsetWidth !== this.state.tableContainerWidth
    ) {
      const newTableContainerWidth = this.tableContainer.offsetWidth;
      const columnWidth = newTableContainerWidth / 3;

      this.setState(({ tableColumns }) => ({
        tableContainerWidth: newTableContainerWidth,
        tableColumns: {
          eventNumber: { label: tableColumns.eventNumber.label, width: columnWidth },
          qualifyingTime: { label: tableColumns.qualifyingTime.label, width: columnWidth },
          selected: { label: tableColumns.selected.label, width: columnWidth },
        },
      }));
    }
  };

  render() {
    return (
      <div className="mb-5" ref={el => (this.tableContainer = el)}>
        <Table
          rowsCount={this.state.tableData.length}
          rowHeight={40}
          headerHeight={50}
          width={this.state.tableContainerWidth}
          height={252}
          touchScrollEnabled
          onColumnResizeEndCallback={this.onEventsTableColumnResizeEndCallback}
          isColumnResizing={false}
        >
          {Object.keys(this.state.tableColumns).slice(0, this.state.numOfColumns).map(key => (
            <Column
              key={key}
              columnKey={key}
              header={<Cell>{this.state.tableColumns[key].label}</Cell>}
              cell={props => <Cell {...props}>{this.state.tableData[props.rowIndex][key]}</Cell>}
              width={this.state.tableColumns[key].width}
              isResizable
            />
            ))}
        </Table>
      </div>
    );
  }
}

Upvotes: 9

Views: 1776

Answers (2)

udayakumar
udayakumar

Reputation: 665

I have updated the Sandbox code, Kindly check the below link, I think it is useful for you

Code Sandbox

Or

    handleCollapseClick = rowIndex => {
    const { collapsedRows } = this.state;

    const shallowCopyOfCollapsedRows = new Set([...collapsedRows]);
    let scrollToRow = rowIndex;
    if (shallowCopyOfCollapsedRows.has(rowIndex)) {
      shallowCopyOfCollapsedRows.delete(rowIndex);
      scrollToRow = null;
    } else {
      shallowCopyOfCollapsedRows.add(rowIndex);
    }

    let numOfExpRows = 0;
    if (collapsedRows.size > 0) {
      numOfExpRows = collapsedRows.size;
    }

    let resetExpChildRow = -1;
    if (collapsedRows.has(rowIndex)) {
      numOfExpRows -= 1;
      resetExpChildRow = rowIndex;
    } else {
      numOfExpRows += 1;
    }

    if (resetExpChildRow === -1) {
      this.setState({
        tableContainerHeight: 250 * numOfExpRows,
        scrollToRow,
        collapsedRows: shallowCopyOfCollapsedRows
      });
    } else {
      this.setState({
        numOfExpRows,
        scrollToRow,
        collapsedRows: shallowCopyOfCollapsedRows,
        expChildRows: {
          ...this.state.expChildRows,
          [rowIndex]: 0
        }
      });
    }
  };

Upvotes: 2

alina
alina

Reputation: 291

I should have commented, but I don't have the required no. of points for that. Have you fixed the issue? Because I just opened the link you have attached and it's working fine

UPDATE Your code works fine. The problem with your approach is that your click event is triggered on the arrow and not on your entire div (or cell). Therefore, you need to be on point and only click on the arrow in order for your row to expand. If you don't want this behavior, I'd suggest putting your click event on the div (or cell)

Here is a working video that demonstrates that it's working fine: https://www.useloom.com/share/2c725f3fede942b09c661a765c72634e

I have taken a screenshot from your video which shows that because you're not clicking on the arrow, that's why your row is not expanding

Upvotes: 0

Related Questions