title: Infinite loading Grid is triggering loadMoreRows at the wrong time
body: <p>I am using react-virtualized to build an <strong>infinite loading grid</strong>, which also uses <code>AutoSizer</code> to handle a dynamic grid width and <code>WindowScroller</code> to enable scrolling with the viewport. However, I am not able to successfully trigger <code>loadMoreRows</code> at the appropriate point in my infinite loading <code>Grid</code>.</p>
  
  <p>I've narrowed down two things:</p>
  
  <ul>
  <li><p>When I set <code>columnCount</code> to 1 instead of 3 in my example below, the loading process seems to work as expected (ie. you scroll near the bottom, it loads another batch, it doesn't load again until we've completely loaded the previous batch).</p></li>
  <li><p>After you have accumulated about 40 rows (indicated at the top of my demo), you can't trigger <code>loadMoreRows()</code> unless you scroll up to an earlier row. To reproduce this behavior, scroll down until the grid has stopped loading new items at the bottom of the grid. Then try scrolling up and then back down again to see how it triggers <code>loadMoreRows()</code> somewhere earlier within the grid.</p></li>
  </ul>
  
  <p>I put together a minimal plunker demo which will render a 3-column grid of random "lorem ipsum" text snippets. There is no end point for this example content, it just loads as much as you will scroll.</p>
  
  <p><strong>Plunker Demo:</strong> <a href="http://plnkr.co/edit/uoRdanlB1rXsgBe2Ej8i" rel="nofollow noreferrer">http://plnkr.co/edit/uoRdanlB1rXsgBe2Ej8i</a></p>
  
  <pre><code>import React, { Component } from 'react';
  import { render } from 'react-dom';
  import { AutoSizer, CellMeasurer, CellMeasurerCache, Grid, InfiniteLoader, WindowScroller } from 'react-virtualized';
  
  const MIN_BATCH_SIZE = 40;
  
  // Return random snippet of lorem ipsum text
  const randText = () =&gt; {
      const text = [
          'Lorem ipsum dolor sit amet.',
          'Consectetur adipisicing elit.',
          'Lorem ipsum dolor sit amet, consectetur adipisicing elit.',
          'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
          'Ut enim ad minim veniam.',
          'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
          'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
          'Excepteur sint occaecat cupidatat non proident.',
          'Sunt in culpa qui officia deserunt mollit anim id est laborum.',
          'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
          'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.'
      ];
  
      return text[Math.floor(Math.random() * text.length)];
  };
  
  // Cell data
  const list = [];
  
  
  // -----------------------------------------------------------------------------
  
  
  // Infinite loading Grid that is AutoSize'd and WindowScroll'd with dynamic cell heights
  class App extends Component {
      constructor(props) {
          super(props);
  
          this.state = {
              columnWidth: 300,
              columnCount: 3,
              rowCount: 0,
              isLoading: false
          };
  
          this._cache = new CellMeasurerCache({
              fixedWidth: true,
              defaultHeight: 30
          });
  
          this._cellRenderer = this._cellRenderer.bind(this);
          this._isRowLoaded = this._isRowLoaded.bind(this);
          this._loadMoreRows = this._loadMoreRows.bind(this);
          this._onResize = this._onResize.bind(this);
          this._onSectionRendered = this._onSectionRendered.bind(this);
      }
  
      componentDidMount() {
          this.setState({ rowCount: 1 });
      }
  
      componentWillUpdate(nextProps, nextState) {
          const { columnCount, rowCount } = this.state;
  
          if (rowCount !== nextState.rowCount) {
              if (nextState.rowCount &gt; rowCount) {
                  // Re-measure the row at the index which was last occupied by "loading" content
                  for (let i = 0; i &lt; columnCount; i++) {
                      this._cache.clear(this._lastLoadingIndex, i);
                  }
              }
          }
      }
  
      render() {
          const { columnCount, columnWidth, rowCount } = this.state;
  
          return (
              &lt;div className="container-fluid"&gt;
                  &lt;h1 className="page-header lead"&gt;RV Infinite Grid&lt;/h1&gt;
  
                  &lt;InfiniteLoader
                      isRowLoaded={this._isRowLoaded}
                      loadMoreRows={this._loadMoreRows}
                      rowCount={rowCount}
                      threshold={5}
                  &gt;
                      {({ onRowsRendered, registerChild }) =&gt; {
                          this._onRowsRendered = onRowsRendered;
  
                          return (
                              &lt;WindowScroller&gt;
                                  {({ height, scrollTop }) =&gt; (
                                      &lt;AutoSizer
                                          disableHeight
                                          onResize={this._onResize}
                                      &gt;
                                          {({ width }) =&gt; (
                                              &lt;Grid
                                                  autoHeight
                                                  width={width}
                                                  height={height}
                                                  scrollTop={scrollTop}
  
                                                  ref={grid =&gt; {
                                                      this._grid = grid;
                                                      registerChild(grid);
                                                  }}
  
                                                  columnWidth={columnWidth}
                                                  columnCount={columnCount}
  
                                                  rowCount={rowCount}
                                                  rowHeight={this._cache.rowHeight}
  
                                                  cellRenderer={this._cellRenderer}
                                                  onSectionRendered={this._onSectionRendered}
                                              /&gt;
                                          )}
                                      &lt;/AutoSizer&gt;
                                  )}
                              &lt;/WindowScroller&gt;
                          );
                      }}
                  &lt;/InfiniteLoader&gt;
              &lt;/div&gt;
          );
      }
  
      _isRowLoaded({ index }) {
          const { rowCount } = this.state;
  
          return index &lt; rowCount - 1;
      }
  
      _loadMoreRows({ startIndex, stopIndex }) {
          const { isLoading } = this.state;
          const delay = 100 + Math.floor(Math.random() * 3000); // random delay to simulate server response time
  
          if (!isLoading) {
              this.setState({
                  isLoading: true
              });
  
              setTimeout(() =&gt; {
                  // Generate some new rows (for this example, we have no actual end point)
                  for (let i = 0; i &lt; MIN_BATCH_SIZE; i++) {
                      list.push([ randText(), randText(), randText() ]);
                  }
  
                  // Cancel the "loading" state and update the`rowCount`
                  this.setState({
                      isLoading: false,
                      rowCount: list.length + 1
                  }, done);
              }, delay);
  
              let done;
              return new Promise(resolve =&gt; done = resolve);
          }
      }
  
      _cellRenderer({ key, rowIndex, columnIndex, parent, style }) {
          const { columnCount, columnWidth, rowCount } = this.state;
          let content;
  
          // Render cell content
          if (rowIndex &lt; rowCount - 1) {
              const cellStyle = Object.assign({}, style, {
                backgroundColor: (rowIndex % 2 ? null : '#eee')
              });
  
              content = (
                  &lt;div style={cellStyle}&gt;
                      &lt;div style={{ padding: '20px' }}&gt;
                          {list[rowIndex][columnIndex] || &lt;em className="text-muted"&gt;empty&lt;/em&gt;}
                      &lt;/div&gt;
                  &lt;/div&gt;
              );
          }
  
          // Render "loading" content
          else if (columnIndex === 0) {
              // Remember this `index` so we can clear its measurements from the cache later
              this._lastLoadingIndex = rowIndex;
  
              const cellStyle = Object.assign({}, style, {
                width: (columnWidth * columnCount), // Give loader the full grid width
                textAlign: 'center'
              });
  
              content = &lt;div style={cellStyle}&gt;Loading...&lt;/div&gt;;
          }
  
          // Render empty cell (for incomplete rows)
          else {
              content = &lt;div style={style} /&gt;;
          }
  
          return (
              &lt;CellMeasurer
                  key={key}
                  cache={this._cache}
                  parent={parent}
                  columnIndex={columnIndex}
                  rowIndex={rowIndex}
              &gt;
                  {content}
              &lt;/CellMeasurer&gt;
          );
      }
  
      _onResize({ width }) {
          this.setState({
              // Subtracting 30 from `width` to accommodate the padding from the Bootstrap container
              columnWidth: (width - 30) / 3
          });
  
          this._cache.clearAll();
          this._grid.recomputeGridSize();
      }
  
      _onSectionRendered({ columnStartIndex, columnStopIndex, rowStartIndex, rowStopIndex }) {
          const { columnCount } = this.state;
  
          const startIndex = rowStartIndex * columnCount + columnStartIndex;
          const stopIndex = rowStopIndex * columnCount + columnStopIndex;
  
          this._onRowsRendered({
              startIndex,
              stopIndex
          });
      }
  }
  
  
  render(&lt;App /&gt;, document.getElementById('root'));
  </code></pre>