2
votes

I can't find any code example or docs that answers this:

  • Achieve almost complete infinite scroll -> unknown # of items, but there is a finite amount that may be infeasible to compute beforehand - e.g. at some point the list needs to stop scrolling
  • Can I trigger first load of data from within InfiniteScroller/List - it seems you need to pass in a data source that is populated with initial page

I am using this example: https://github.com/bvaughn/react-virtualized/blob/master/docs/creatingAnInfiniteLoadingList.md

and: https://github.com/bvaughn/react-virtualized/blob/master/source/InfiniteLoader/InfiniteLoader.example.js

along with CellMeasurer for dynamic height: https://github.com/bvaughn/react-virtualized/blob/master/source/CellMeasurer/CellMeasurer.DynamicHeightList.example.js

The docs for InfiniteLoader.rowCount say: "Number of rows in list; can be arbitrary high number if actual number is unknown."

So how do you indicate there are no more rows.

If anyone can post an example using setTimeout() to simulate dynamic loaded data, thanks. I can likely get CellMeasurer working from there.

Edit

This doesn't work the way react-virtualized creator says it should or the infinite loading example implies.

Calls:

  • render(): rowCount = 1
  • _rowRenderer(index = 0)
  • _isRowLoaded(index = 0)
  • _loadMoreRows(startIndex = 0, stopIndex = 0)
  • _rowRenderer(index = 0)
  • end

Do I need to specify a batch size or some other props?

class HistoryBrowser extends React.Component
{
    constructor(props,context,updater)
    {
        super(props,context,updater);
        this.eventEmitter = new EventEmitter();
        this.eventEmitter.extend(this);
        this.state = {
            history: []
        };
        this._cache = new Infinite.CellMeasurerCache({
            fixedWidth: true,
            minHeight: 50
        });
        this._timeoutIdMap = {};
        _.bindAll(this,'_isRowLoaded','_loadMoreRows','_rowRenderer');

    }


    render()
    {
        let rowCount = this.state.history.length ? (this.state.history.length + 1) : 1;
        return <Infinite.InfiniteLoader
            isRowLoaded={this._isRowLoaded}
            loadMoreRows={this._loadMoreRows}
            rowCount={rowCount}
        >
            {({ onRowsRendered, registerChild }) =>
                <Infinite.AutoSizer disableHeight>
                    {({ width }) =>
                        <Infinite.List
                            ref={registerChild}
                            deferredMeasurementCache={this._cache}
                            height={200}
                            onRowsRendered={onRowsRendered}
                            rowCount={rowCount}
                            rowHeight={this._cache.rowHeight}
                            rowRenderer={this._rowRenderer}
                            width={width}
                        />}
                </Infinite.AutoSizer>}
        </Infinite.InfiniteLoader>
    }

    _isRowLoaded({ index }) {
        if (index == 0 && !this.state.history.length)
            // No data yet, force load
            return false;
    }

    _loadMoreRows({ startIndex, stopIndex }) {
        let self = this;
        for (let i = startIndex; i <= stopIndex; i++) {
            this.state.history[startIndex] = {loading: true};
        }

        const timeoutId = setTimeout(() => {
            delete this._timeoutIdMap[timeoutId];

            for (let i = startIndex; i <= stopIndex; i++) {
                self.state.history[i] = {loading: false, text: 'Hi ' + i };
            }

            promiseResolver();
        }, 10000);

        this._timeoutIdMap[timeoutId] = true;

        let promiseResolver;

        return new Promise(resolve => {
            promiseResolver = resolve;
        });
    }

    _rowRenderer({ index, key, style }) {
        let content;
        if (index >= this.state.history.length)
            return <div>Placeholder</div>
        else if (this.state.history[index].loading) {
            content = <div>Loading</div>;
        } else {
            content = (
                <div>Loaded</div>
            );
        }

        return (
            <Infinite.CellMeasurer
                cache={this._cache}
                columnIndex={0}
                key={key}
                rowIndex={index}
            >
                <div key={key} style={style}>{content}</div>
            </Infinite.CellMeasurer>
        );
    }
}
1

1 Answers

3
votes

The recipe you linked to should be a good starting place. The main thing its missing is an implementation of loadNextPage but that varies from app to app based on how your state/data management code works.

Can I trigger first load of data from within InfiniteScroller/List - it seems you need to pass in a data source that is populated with initial page

This is up to you. IMO it generally makes sense to just fetch the first "page" of records without waiting for InfiniteLoader to ask for them- because you know you'll need them. That being said, if you give InfiniteLoader a rowCount of 1 and then return false from isRowLoaded it should request the first page of records. There are tests confirming this behavior in the react-virtualized GitHub.

The docs for InfiniteLoader.rowCount say: "Number of rows in list; can be arbitrary high number if actual number is unknown."

So how do you indicate there are no more rows.

You stop adding +1 to the rowCount, like the markdown file you linked to mentions:

// If there are more items to be loaded then add an extra row to hold a 
loading indicator.
  const rowCount = hasNextPage
    ? list.size + 1
    : list.size