0

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;
}
grooveplex
  • 2,492
  • 4
  • 28
  • 30

1 Answers1

1

I'm not sure if it's the best solution but I found that adding the following code to Vlistsolved my issue:

componentDidUpdate() {
    this.viewPortRef.current.scrollTop = this.state.start * this.props.itemHeight;
}