I'm using expandable panels (Material-UI) in rows with a react virtualized list and have been having issues with the heights auto-adjusting. I've read several SO posts and some issues on dynamic row heights on the react-virtualized site, but I have a specific problem where it seems like there's an 'off by one' issue with when the row height is adjusted after panel is expanded/collapsed.
Here's the expected behavior:
- Row panel expanded by default.
- User clicks expandable panel row.
- Row panel collapses.
- Row height adjusts to panel collapse.
Here's the actual behavior FOR THE FIRST CLICK:
- Row panel expanded by default.
- User clicks expandable panel row.
- Row panel collapses.
- Row height does NOT adjust to panel collapse.
- HOWEVER, on subsequent clicks the row height DOES adjust, but to the 'opposite' state, which leads to an inconsistency - i.e when the the row panel is clicked to expand again, the row height is adjusted to the row height as if it were collapsed, and vice versa. So when the panel is collapsed there's a bunch of white space after it, and when it's technically expanded the row height is too small to see the content.
I'm not sure what other info to include besides posting code and noting that the onRowClick() IS firing when the panels are collapsed/expanded.
Here's the parent component:
import React, { Component } from 'react';
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
import List from 'react-virtualized/dist/commonjs/List';
import { CellMeasurer, CellMeasurerCache } from 'react-virtualized/dist/commonjs/CellMeasurer';
import EquipSummaryRow from './EquipSummaryRow';
import './EquipSummary.css';
class EquipSummary extends Component {
constructor(props) {
super(props);
this.cache = new CellMeasurerCache({
fixedWidth: true,
});
this.rowRenderer = this.rowRenderer.bind(this);
this.getDatum = this.getDatum.bind(this);
this.onRowClick = this.onRowClick.bind(this);
}
getDatum(index) {
const list = this.props.equipData;
return list[index];
}
saveRef = (ref) => this.containerNode = ref;
saveListRef = (ref) => {
this.list = ref;
}
componentDidUpdate() {
console.log('component updated');
this.cache.clearAll();
this.list.recomputeRowHeights();
}
onRowClick(e, index) {
e.preventDefault();
this.cache.clear(index);
this.list.recomputeRowHeights();
this.list.forceUpdateGrid();
}
rowRenderer({ index, key, parent, style }) {
const datum = this.getDatum(index);
return (
<div key={key} style={style}>
<CellMeasurer
cache={this.cache}
columnIndex={0}
key={key}
rowIndex={index}
parent={parent}
>
{({ measure }) => (
<EquipSummaryRow
onClick={(e, idx) => this.onRowClick(e, idx)}
measure={measure}
datum={datum}
index={index}
/>
)}
</CellMeasurer>
</div>
);
}
render() {
console.log('rendering..');
return (
<div className="EquipSummary-AutoSizer" ref={this.saveRef}>
<AutoSizer>
{({ width, height }) => (
<List
ref={this.saveListRef}
width={width}
height={height}
rowHeight={this.cache.rowHeight}
rowCount={this.props.equipData.length}
rowRenderer={this.rowRenderer}
deferredMeasurementCache={this.cache}
equipData={this.props.equipData}
/>
)}
</AutoSizer>
</div>
);
}
}
export default EquipSummary;
And here's the component that represents a row:
import React, { Component } from 'react';
import {
Table,
TableBody,
TableHeader,
TableHeaderColumn,
TableRow,
TableRowColumn,
} from 'material-ui/Table';
import { MuiThemeProvider } from 'material-ui/styles';
import ExpansionPanel from '@material-ui/core/ExpansionPanel';
import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary';
import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails';
import Typography from '@material-ui/core/Typography';
class EquipSummaryRow extends Component {
render() {
const { datum } = this.props;
return (
<div>
<ExpansionPanel
defaultExpanded
onChange={e => this.props.onClick(e, this.props.index)}
>
<ExpansionPanelSummary expandIcon={<div>|</div>}>
<Typography>{`${datum.type} (id: ${datum.instance}, points: ${datum.points.length})`}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Table>
<TableHeader
displaySelectAll={false}
adjustForCheckbox={false}
>
<TableRow>
<TableHeaderColumn>Device</TableHeaderColumn>
<TableHeaderColumn>Object ID</TableHeaderColumn>
<TableHeaderColumn>Type</TableHeaderColumn>
<TableHeaderColumn>Name</TableHeaderColumn>
<TableHeaderColumn>Description</TableHeaderColumn>
<TableHeaderColumn>Units</TableHeaderColumn>
<TableHeaderColumn>Value</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody
displayRowCheckbox={false}
>
{datum.points.map((row, index) => (
<TableRow key={row.id}>
<TableRowColumn>{row.device}</TableRowColumn>
<TableRowColumn>{row.objectID}</TableRowColumn>
<TableRowColumn>{row.type}</TableRowColumn>
<TableRowColumn>{row.name}</TableRowColumn>
<TableRowColumn>{row.description}</TableRowColumn>
<TableRowColumn>{row.units}</TableRowColumn>
<TableRowColumn>{row.value}</TableRowColumn>
</TableRow>
))}
</TableBody>
</Table>
</ExpansionPanelDetails>
</ExpansionPanel>
</div>
);
}
}
export default EquipSummaryRow;
Could this be an issue with how I'm using the cache? I've been beating my head with this so any suggestions appreciated!