9

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>
    );
  }
}
Matt
  • 8,195
  • 31
  • 115
  • 225
  • It's working fine – udayakumar Jun 12 '18 at 10:58
  • @udayakumar check [this](https://imgur.com/KZSdg0b) and [this](https://imgur.com/wzmOXbM) recording – Matt Jun 12 '18 at 11:12
  • Yeah, I see your issues. The first click is working for me. You just have to main exactly at the black arrow. About the rendering problem, I think it has to do with the inner component being scroll. The scrolling overall is quite lagged for me – diogenesgg Jun 12 '18 at 12:02

2 Answers2

2

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
        }
      });
    }
  };
udayakumar
  • 639
  • 1
  • 7
  • 16
  • Thanks, but when I tried your example I had the same issue. I made a [recording](https://i.imgur.com/RBOjzHl.gif) with your sandbox where you can see the issue. – Matt Jun 13 '18 at 09:25
  • Okay, I will check it @Matt – udayakumar Jun 13 '18 at 09:40
  • Thank you, I checked your codesandox, but there is something strange with height of inner table (second level). I made a new [recording](https://i.imgur.com/S5uYKkI.gif) where you can see it. Horizontal scrollbar of inner table is not visible after row expand. If you expand/close row in inner table, the height of inner table is not correct. Anyway, I think we are close to the solution. I am in the middle of something and can't check your code, but I will try to check it out soon. – Matt Jun 13 '18 at 12:24
  • Can you check it now? @Matt – udayakumar Jun 13 '18 at 12:37
  • I think we reached, kindly check it. @Matt – udayakumar Jun 13 '18 at 12:44
  • 1
    It looks better now. Thank you for your help, your answer should be awarded. – Matt Jun 13 '18 at 12:49
0

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

alina
  • 291
  • 2
  • 9
  • Which browser are you using? – Matt Jun 12 '18 at 10:00
  • I am using chrome browser – alina Jun 12 '18 at 10:09
  • I don't believe you don't have this issue... Do you think you can create a short `.gif` recording and upload it to imgur, like [this](https://imgur.com/KZSdg0b)? – Matt Jun 12 '18 at 10:16
  • Okay let me I think. There can be a possibility that I misunderstood your problem – alina Jun 12 '18 at 10:39
  • You can see this issue in [this](https://imgur.com/KZSdg0b) and [this](https://imgur.com/wzmOXbM) recordings – Matt Jun 12 '18 at 10:41
  • @Matt Here is the video: https://www.useloom.com/share/2c725f3fede942b09c661a765c72634e As you can see in this video, the clicks are working pretty fine at my end. I have used Chrome for this and I am clicking everything just once in the video – alina Jun 13 '18 at 04:46
  • Thanks for your answer and recording. I made a new [recording](https://i.imgur.com/sRsH3lZ.gif) where you can see I moved click event to entire cell and the issue still persists. When I checked your recording I realized there is this issue only if I do second click on last two items in inner table. In your recording your second click in inner table is on second or third item. Thanks for this, I am about to update my question based on this. – Matt Jun 13 '18 at 09:47
  • @Matt Umm I wish I could've helped you out in reproducing this bug. I just did what you mentioned in your comment, clicking on last two items in the inner table and it's still working fine :/ https://www.useloom.com/share/97cf2ec52e204ee0a6f178fa64ef127a I have basically clicked on all the items and it's working fine – alina Jun 13 '18 at 10:17
  • Thank you again for your recording and response. You are right, it really works if you expand first row of main table. If you try to expand third or fourth row of main (first) table, you can see the issue. – Matt Jun 13 '18 at 10:24