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
Most helpful comment
You can add ref
the
bindListReffunctionAt last, call
forceUpdateGridinComponentDidUpdate