react-virtualized
react-virtualized copied to clipboard
List doesn't update display or row heights when data updated.
Setup
I have this List
that uses CellMeasurer
to compute row heights dynamically.
<WindowScroller ref={ref => (this.windowScroller = ref)}>
{({ height, isScrolling, onChildScroll, scrollTop }) => (
<AutoSizer disableHeight={true}>
{({ width }) => (
<div>
<List
autoHeight={true}
height={height}
isScrolling={isScrolling}
onScroll={onChildScroll}
scrollTop={scrollTop}
rowCount={contacts.length}
rowHeight={this._cache.rowHeight}
rowRenderer={this._rowRenderer}
width={width}
deferredMeasurementCache={this._cache}
overscanRowCount={0}
/>
</div>
)}
</AutoSizer>
)}
</WindowScroller>
Problem The list doesn't update either display or row heights when data has changed. For the list to change, I have to scroll the window manually. I've tried to clear the cache, updatePosition and forceRender but still has no effect.
Temporary Solution
Since scrolling to update isn't acceptable, I created a hack in the componentDidUpdate
to be able to update the list when data changes.
/**
* Since virtual list doesn't update till it scrolls, programmatically scroll window to
* trigger update.
*/
public componentDidUpdate(prevProps: Props) {
const becameVisible = this.props.isVisible && !prevProps.isVisible;
const contactChanged = this.props.contacts !== prevProps.contacts;
if (this.windowScroller && (becameVisible || contactChanged)) {
this._cache.clearAll();
this.windowScroller!.updatePosition();
window.scrollTo(window.scrollX, window.scrollY + 5);
window.scrollTo(window.scrollX, window.scrollY - 5);
}
}
I have the same problem using ReactVirtualized.Grid. I've found that even when using unique keys, the grid display won't update unless the number of rows is different. Example code at https://codesandbox.io/s/3qlqz9o8nq
I tried ref.forceUpdate() but it only works first time:
this.setState({selectedIndex:index}, ()=>list.forceUpdate())
function({ index, key, style }) { const selected = index===this.state.selectedIndex; return ( <div className={cx("sitcontrol_list_item", selected && "sitcontrol_selected")} data-index={index} key={key} style={style}>{this.props.data[index]}</div> ); }
I made it work rebinding item renderer before setState:
this.itemRenderer = this.itemRedenrer.bind(this)
Inline arrow function may work too
You can add ref
<List
ref={this.bindListRef}
autoHeight={true}
height={height}
isScrolling={isScrolling}
onScroll={onChildScroll}
scrollTop={scrollTop}
rowCount={contacts.length}
rowHeight={this._cache.rowHeight}
rowRenderer={this._rowRenderer}
width={width}
deferredMeasurementCache={this._cache}
overscanRowCount={0}
/>
the bindListRef
function
bindListRef = ref => {
this.list = ref;
};
At last, call forceUpdateGrid
in ComponentDidUpdate
componentDidUpdate() {
if (this.list) {
this.list.forceUpdateGrid();
}
}
@superman66 It works! Thank you for solving my problem.
for me this didn't work, so I dynamically set an "artificial" key on the List
, and I bump it by 1 to programmatically force a rerender
class MyComponent extends React.Component {
this.listKey = 0
componentDidUpdate() { this.listKey += 1 }
...
render() {
<List key={this.listKey} some more props here />
}
}
Similar problem here, and when my data updated, row height which should also change doesn't update. Solve this by
componentDidUpdate() {
if (this.list) {
this._cache.clearAll(); // re-calculate cache & rowHeight
this.list.forceUpdateGrid();
}
}
@y2x33 Use react-virtualized's CellMeasurer
and pass your element the measure
function. From inside the component, when you need to re-measure you can do so for just that one row.
e.g.
componentDidUpdate(pProps) {
if (this.props.text !== pProps.text) {
this.props.measure();
}
}
Or you can do this for when dynamic content loads in:
handleImageLoad = () => {
// an image has loaded, probably changing the dimensions of this virtualized item!
this.props.measure();
}
render() {
const { image } = this.props;
return (
<div>
<p>Some other content that shows up first</p>
<img src={image.src} onload={this.handleImageLoad} />
</div>
}
passing in an anonymous or a no-op function () => {}
to onRowsRendered
prop fixes this problem
Setup I have this
List
that usesCellMeasurer
to compute row heights dynamically.<WindowScroller ref={ref => (this.windowScroller = ref)}> {({ height, isScrolling, onChildScroll, scrollTop }) => ( <AutoSizer disableHeight={true}> {({ width }) => ( <div> <List autoHeight={true} height={height} isScrolling={isScrolling} onScroll={onChildScroll} scrollTop={scrollTop} rowCount={contacts.length} rowHeight={this._cache.rowHeight} rowRenderer={this._rowRenderer} width={width} deferredMeasurementCache={this._cache} overscanRowCount={0} /> </div> )} </AutoSizer> )} </WindowScroller>
Problem The list doesn't update either display or row heights when data has changed. For the list to change, I have to scroll the window manually. I've tried to clear the cache, updatePosition and forceRender but still has no effect.
Temporary Solution Since scrolling to update isn't acceptable, I created a hack in the
componentDidUpdate
to be able to update the list when data changes./** * Since virtual list doesn't update till it scrolls, programmatically scroll window to * trigger update. */ public componentDidUpdate(prevProps: Props) { const becameVisible = this.props.isVisible && !prevProps.isVisible; const contactChanged = this.props.contacts !== prevProps.contacts; if (this.windowScroller && (becameVisible || contactChanged)) { this._cache.clearAll(); this.windowScroller!.updatePosition(); window.scrollTo(window.scrollX, window.scrollY + 5); window.scrollTo(window.scrollX, window.scrollY - 5); } }
The 5px window scroll helped me a lot! Thanks :smile:
I am facing the same issue. Seems like a very tricky thing for react-virtualized to handle.
Calling the method recomputeRowHeights
did the trick for me. The function that was used to calculate height had some state dependencies that was not being applied after an update to the state.