Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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>
    );
  }
}
like image 755
Matt Avatar asked Jun 04 '18 14:06

Matt


1 Answers

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
        }
      });
    }
  };
like image 90
udayakumar Avatar answered Oct 12 '22 13:10

udayakumar