Resetting row heights causes incorrect row offset to get calculated

Viewed 3102

Inside of an InfiniteLoader I have a List, whose rowRenderer callback creates a <div> wrapped by CellMeasurer (I'm also using a cache for that). The rows can vary wildly in size, but this setup is working well for scrolling purposes.

However, elements within each row can dynamically change in height based on user input. In those cases, I call:

cellMeasurerCache.clear(rowIndex, 0);
this._listRef.recomputeRowHeights(rowIndex);

But in some cases, this causes the rows to jump around -- this is because the top values are getting calculated incorrectly. For example, I'm displaying a document with the following rows rendered in List:

rowIdx        height       top
  3            668px       420px
  4           8547        1088
  5           2420        9635

Then the height of row 4 increases slightly (~50px). After running the above code with rowIndex == 4, I see the following rows:

rowIdx        height       top
  5           2420px      3088px
  6           1156        5508
  7           4718        6664
  8           1050       11382

As you can see, row 5 now has an incorrect top value, causing the rows to jump around. I'm assuming that the height of row 4 did not get measured correctly.

What's wrong?


Update 2017-11-08: It appears that calling cellMeasurerCache.clear(rowIndex, 0) replaces the height of that row with the value of estimatedRowSize, which is very different. It doesn't seem to re-measure, contrary to what the docs say.

CellMeasurer._maybeMeasureCell() isn't called for row 4 after it gets the default height. My rowRenderer callback (which creates CellMeasurer around my content) is only invoked starting at row 5. So the code has made the decision of which rows to show with row 4 having the much smaller default height, instead of calculating beforehand.

1 Answers

Not sure if this is the correct/best way to handle this, but I was able to get this working like so:

First, I used the callback version of CellMeasurer, storing the measure callback for later use (stored per row):

rowRenderer = ({ key, index, style, isScrolling, parent }) => {
  const getContent = measure => {
    // store callback for later use
    this._measureCallbacks[index] = measure;

    // ... get your content ...
    return content;
  };

  return (
    <CellMeasurer
      key={key}
      cache={cellMeasurerCache}
      columnIndex={0}
      parent={parent}
      rowIndex={index}
    >
      {({ measure }) => (
        <div key={key} style={style}>
          {getContent(measure)}
        </div>
      )}
    </CellMeasurer>
  );
};

Later, when a row height changes, instead of calling cellMeasurerCache.clear(), I run:

// tell row to re-measure
this._measureCallbacks[rowIndex]();
// tell list to recompute row offsets
this._listRef.recomputeRowHeights(minRow);
Related