Closed marsonmao closed 6 years ago
I'm sharing part of my codes to demonstrate how to overcome dynamic height items:
componentDidMount() {
if (this.props.chatObjects.length !== 0) {
this.setState({
scrollToIndex: this.props.chatObjects.length - 1,
});
// NOTE should 100% be true
this.checkIfRerender();
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (
prevProps.chatObjects.length !== this.props.chatObjects.length &&
this.enableAutoScroll
) {
this.setState({
scrollToIndex: this.props.chatObjects.length - 1,
});
}
if (
prevProps.chatObjects.length !== this.props.chatObjects.length &&
this.isFetching
) {
this.isFetching = false;
this.setState({
scrollToIndex: this.props.chatObjects.length - prevProps.chatObjects.length,
});
}
if (prevProps.hasMoreChatObjects && !this.props.hasMoreChatObjects) {
this.isFetching = false;
}
this.checkIfRerender();
}
checkIfRerender = () => {
let needRerender = false;
Object.entries(this.itemCache).forEach(([id, item]) => {
if (item.fresh) {
this.itemCache[id].fresh = false;
needRerender = true;
}
});
if (needRerender) {
this.li.recomputeSizes();
this.li.forceUpdate();
this.forceUpdate();
if (this.state.scrollToIndex !== null) {
// HACK 100% relying on tiny-virtual-list's codes
this.li.setState({
offset: this.li.getOffsetForIndex(this.state.scrollToIndex, undefined, this.props.chatObjects.length),
scrollChangeReason: 'requested',
});
}
}
}
getItemSize = (index) => {
const { chatObjects } = this.props;
const chatObject = chatObjects[index];
return (this.itemCache[chatObject.messageId] && this.itemCache[chatObject.messageId].height) || magicItemDefaultHeight;
}
onItemsRendered = ({ startIndex, stopIndex }) => {
// HACK setTimeout is to avoid being affected by scroll event
setTimeout(() => { this.checkIfRerender(); }, 0);
}
setItem = (id, height) => {
let item = this.itemCache[id] || {};
if (
!item.height ||
(item.height && item.height !== height)
) {
item = {
height,
fresh: true,
};
}
this.itemCache[id] = item;
}
renderItem = ({ index, style }) => {
const { chatObjects, chatStatus } = this.props;
const chatObject = chatObjects[index];
return (
<div key={chatObject.messageId} style={style}>
<ChatObject
key={chatObject.messageId}
chatStatus={chatStatus}
chatObject={chatObject}
onDidMount={(height) => { this.setItem(chatObject.messageId, height); }}
onDidUpdate={(height) => { this.setItem(chatObject.messageId, height); }}
/>
</div>
);
}
render() {
const { chatObjects } = this.props;
if (chatObjects.length === 0) {
return <div style={{ width: '100%', height: '100%' }} />;
}
return (
<AutoSizer>
{({ width, height }) => (
<VirtualList
ref={this.addVirtualList}
width={width}
height={height}
itemCount={chatObjects.length}
itemSize={this.getItemSize}
renderItem={this.renderItem}
onItemsRendered={this.onItemsRendered}
onScroll={this.onScroll}
scrollToIndex={this.state.scrollToIndex}
overscanCount={20}
/>
)}
</AutoSizer>
);
}
react-tiny-virtual-list
uses PureComponent, and as such, it has no way to know when the sizes of your items changes when you use a function to get the item sizes.
You can call forceUpdate
on the instance or pass it an extra prop that changes whenever the size of your items changes.
See https://github.com/clauderic/react-tiny-virtual-list#common-issues-with-purecomponent
Thank you @clauderic . Just want to make sure my solution is correct because I think it's really uncommon to use forceUpdate
in componentDidMount
and componentDidUpdate
. Also want to make sure that I correctly use recomputeSizes
. Hope my codes could help other people who also have dynamic height items, this is so far the minimal setup I could create.
componentDidMount()
and cache the heights.itemSize={this.getItemSize}
returns the cached heights.itemSize
is invoked before step 1 (it is invoked insidethis.getStyle(index)
), so the first list render gets the wrong height.recomputeSizes()
andforceUpdate()
in the listcomponentDidUpdate
to triggeritemSize
again, so that the correct height could be used.The question: is this necessary? I'd like to know if it's a must to always render twice since the item height can't be known in the first render?
My codes are below for reference: