I'm trying to create a virtualized grid that only shows data that fits within the viewport. In the Vlist
object, I'm getting a RefObject
of the viewport and using viewPortRef.current.scrollTop
to help calculate the index to determine which items in the list to display in the viewport.
However, when I run my code and scroll I see the new items added but they flicker. Additionally, if I keep scrolling the whole thing bugs out and will either stop displaying anything or reset back to the top.
When I debug the scrolling I'm seeing that my viewPortRef.current.scrollTop
value keeps alternating between two values each time the component renders.
I can't figure out why scrollTop
would alternate values like that. Am I referencing the wrong element? Am I making a mistake using scrollTop to find the scroll position?
What's especially confusing is that I'm basically just trying to copy the virtual grid found in this answer: https://stackoverflow.com/a/50615182/11137097
That code snippet seems to work fine on its own, but I get the issues described above when I try to bring it over into my website.
Here's my code:
class Item extends React.Component
{
props: {
top: any;
itemHeight: any;
label: any;
}
constructor(props)
{
super(props);
}
render()
{
return (
<div className="item" style={{ top: this.props.top, height: this.props.itemHeight }}>
{this.props.label}
</div>)
}
}
class Vlist extends React.Component<{}, { [property: string]: number }>
{
props: {
itemHeight: number;
containerStyle: object;
data: any[];
numberVisibleItems: number;
}
state: {
start: number;
end: number;
}
private viewPortRef: React.RefObject<HTMLDivElement>;
private scrollTop: number;
constructor(props)
{
super(props);
this.state = {
start: 0,
end: this.props.numberVisibleItems,
}
this.scollPos = this.scollPos.bind(this)
this.createRows = this.createRows.bind(this)
this.props.data = props.data;
this.viewPortRef = React.createRef();
this.scrollTop = 0;
}
scollPos()
{
this.scrollTop = Math.trunc(this.viewPortRef.current.scrollTop);
let currentIndx = Math.trunc(this.scrollTop / this.props.itemHeight)
currentIndx = currentIndx - this.props.numberVisibleItems >= this.props.data.length ? currentIndx - this.props.numberVisibleItems : currentIndx;
if (currentIndx !== this.state.start)
{
this.setState({
start: currentIndx,
end: currentIndx + this.props.numberVisibleItems >= this.props.data.length ? this.props.data.length - 1 : currentIndx + this.props.numberVisibleItems,
}
)
}
}
createRows()
{
let result = [];
for (let i = this.state.start; i <= this.state.end; i++)
{
if (this.props.data.length > 0) {
let item = this.props.data[i];
result.push(<Item key={i} label={item.name} top={i * this.props.itemHeight} itemHeight={this.props.itemHeight} />);
}
}
return result;
}
render()
{
let rows = this.createRows();
return (
<div className="span11">
<div ref={this.viewPortRef} className="viewPort" onScroll={this.scollPos} >
<div className="itemContainer" style={this.props.containerStyle}>
{rows}
</div>
</div>
</div>
);
}
}
CSS Style:
.viewPort {
//position: relative;
width: 100%;
height: 66.67vh;
border: solid 1px;
overflow-y: scroll;
}
.itemContainer {
//position: absolute;
width: calc(100% - 10px);
background-color: azure;
}
.item {
//position: relative;
background-color: beige;
border: solid 1px;
width: 100%;
text-align: center;
height: 66.67vh;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}