Categories
fixed-data-table javascript reactjs

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