kewlking
kewlking

Reputation: 412

CellMeasurer with function child ({ measure }) => <div />) renders excessive number of times

REF: demo at https://codesandbox.io/s/1vy7ljo877

The demo uses 2 approaches to render some images.

The first row renders an element inside CellMeasurer while the second row renders a function with signature: ({ measure }) => )

I find that for the function render, while the size is calculated correctly, the render method gets called n^2 # of times where n is approximately the number of items that it tries to display.

If deferredMeasurementCache is turned on, the problem gets exponentially worse since n becomes the size of the collection.

The above results in crazy long page load times and very sluggish behavior when scrolling.

Is this normal behavior or am I implementing the function inside CellMeasurer incorrectly?

Upvotes: 0

Views: 1559

Answers (1)

bvaughn
bvaughn

Reputation: 13497

If deferredMeasurementCache is turned on, the problem gets exponentially worse since n becomes the size of the collection.

If you're not passing the CellMeasurerCache to Grid as a deferredMeasurementCache prop, then Grid isn't guaranteed to work correctly. It's necessary for it to know about the cache for a couple of cases. (Maybe check out this part of the docs to make sure you aren't misunderstanding how CellMeasurer is supposed to work when used to calculate the width/height of a row/column.)

That being said, what you're describing sounds like a bug, but I'm not sure it actually is. At a high level, it's caused by the CellMeasurer measure method calling Grid.recomputeGridSize() which synchronously triggers forceUpdate() (causing all visible cells to re-render).

At first I thought the solution might be to just debounce or queue a setState action instead. Unfortunately that only pushes the problem away a little; the possibility still exists. I can't debounce for too long, or I risk the Grid showing cells to the user that are clearly the wrong size. (A debounce that's too big might even allow a user to keep scrolling ahead of it, outrunning the re-render.) It's also possible that images will load far apart (after a delay) in a real app, defeating any debounce I might do anyway.

The problem here is that any time a cell reports it has a different size, the Grid needs to relayout the other cells (potentially shifting them up or down). In this simple example it seems unnecessary- because all images end up having a uniform width and height. This isn't typical though. Since you've configured your CellMeasurerCache to measure height, and since the height of each column impacts the height of the row (and thus all other columns, as explained in the docs I linked to above), Grid has to re-render all cells each time one reports a change.

Put another way, if you added more rows, and changed your example to be more like this:

const makeImages = (count=10, startIndex=0) => {
  const width = 100;
  const imagesArray = _.times(count, (index) => {
    const height = 100 + Math.round(Math.random() * 200);
    return {
      key: startIndex+index,
      src: `http://placehold.it/${width}x${height}/${((1<<24)*Math.random()|0).toString(16)}/fff?text=${startIndex+index}`,
    }
  });

  return imagesArray;
};

Then maybe it's easier to see why a change to a cell in row 1, column 1 could potential impact the height of all cells in row 1, and the position of all cells in the grid.

Upvotes: 2

Related Questions