React-virtualized: <CellMeasurer /> doesn't render new height when index changes

Created on 19 Jan 2017  路  2Comments  路  Source: bvaughn/react-virtualized

Description:

I am trying to render a <List /> using the new idCellSizeCache, but I cannot seem to get the <CellMeasurer /> to use the same height based on the cache. Even clearing the cache doesn't work. Maybe I am doing something fundamentally wrong, but I have isolated the issue in a seperate repo: https://github.com/inooid/react-virtualized-doesnt-remeasure-height

Use case:

I have variable items with variable heights, so I am using the <CellMeasurer /> to calculate the height of the item based on a fixed width. I now want to remove an item from the list, so I remove an item based on the index from the list and re-render. The list obviously shifts, because an item is removed, but the height is still set to the index.

For example:

const items = [1, 2, 3, 4];

/*
Renders something like this:
--------------------------------
|               1              |
--------------------------------
|                              |
|               2              |
|                              |
--------------------------------
|               3              |
--------------------------------
|                              |
|                              |
|               4              |
|                              |
|                              |
--------------------------------
*/

// I then proceed to remove item with 3
const items = [1, 2, 4];
/*

Expected:
--------------------------------
|               1              |
--------------------------------
|                              |
|               2              |
|                              |
--------------------------------
|                              |
|                              |
|               4              |
|                              |
|                              |
--------------------------------

Reality:
--------------------------------
|               1              |
--------------------------------
|                              |
|               2              |
|                              |
--------------------------------
|               4              |
--------------------------------
*/

// The height of item number 4 has now changed to the one that was the height of
// item number 3. 

Example gif (from the provided repo):

doesnt-recalculate


Like I said, maybe I am doing something fundamentally wrong, but I cannot seem to figure out the problem. Once I get a better grasp of what's going on, I might be able to contribute to clarifying the documentation 馃憤

Most helpful comment

Hi @inooid,

What a very detailed issue! Thanks for taking the time to write it so clearly and provide example code. 馃榿

I'm sorry you lost a few days to this! I wish I had been able to help sooner. From my perspective- what you've described is working as expected, but I realize that it can be less than intuitive to someone new to this aspect of the library.

For what it's worth, I've tried to outline this in the docs. I mention pure components on the main docs page and recomputeRowHeights on the List docs page. If you have ideas for how this could be covered more clearly in the docs though- I welcome pull requests from the community. I'd love for you to contribute!

It sounds like you've worked this issue out for yourself in the meanwhile so I'm going to close it. We can talk more here if you have follow up questions though!

All 2 comments

After spending 3 days working on it, I figured out a solution (not sure if it's the right solution though).

If you're implementing the idCellSizeCache of <CellMeasurer /> with a <List /> and your rowCount (for example removing some item from the array) changes, then you might want to call recomputeRowHeights(index) on the <List /> so it recomputes the rowHeights.

So you'll probably have to do something like this:

<List
  ref={(thisList) => this.list = thisList}
  { ...allTheOtherRequiredProps }
/>

Your parent component then has access to this.list and can call this.list.recomputeRowHeights() whenever you remove or add something to the array.

Smallest example I could think of:

import React from 'react';
import {
  List,
  CellMeasurer,
  idCellMeasurerCellSizeCache as idCellSizeCache,
} from 'react-virtualized';

class App extends React.Component {
  state = {
    items: [
      { id: 901, text: 'Item 1', height: 50, },
      { id: 902, text: 'Item 2', height: 100 },
      { id: 903, text: 'Item 3', height: 25, },
      { id: 904, text: 'Item 4', height: 200 },
    ]
  }

  constructor(...args) {
    super(...args);

    this.cache = new idCellSizeCache({
      indexToIdMap: (index) => this.state.items[index].id,
      uniformColumnWidth: true,
    });
  }

  removeItem = () => {
    const index = prompt('What index do you want to remove?');

    if (index) {
      const indexNum = Number(index);
      // Remove the element from the state
      this.setState((prevState) => ({
        ...prevState,
        items: [
          ...prevState.items.slice(0, indexNum),
          ...prevState.items.slice(indexNum + 1, prevState.items.length),
        ],
      }));

      // Recalculate the new heights from the index, based on the index changes
      this.list.recomputeRowHeights(index);
    }
  }

  render() {
    const { items } = this.state;
    const length = items.length;

    const rowRenderer = ({ index, style }) => {
      const item = items[index];
      const color = `rgba(255, 165, 0, ${index * 0.2}`

      return (
        <div key={item.id} style={style}>
          <div style={{ height: item.height, backgroundColor: color }}>
            { `${index}. ${item.text} (height: ${item.height}px)` }
          </div>
        </div>
      );
    };

    return (
      <div>
        <CellMeasurer
          cellSizeCache={this.cache}
          width={300}
          rowCount={length}
          columnCount={1}
          cellRenderer={rowRenderer}
        >
          { ({ getRowHeight }) => (
            <List
              ref={(list) => this.list = list}
              width={300}
              height={800}
              rowCount={length}
              rowHeight={getRowHeight}
              rowRenderer={rowRenderer}
            />
          ) }
        </CellMeasurer>

        <button onClick={this.removeItem}>Remove an item</button>
      </div>
    );
  }
}

export default App;

Hi @inooid,

What a very detailed issue! Thanks for taking the time to write it so clearly and provide example code. 馃榿

I'm sorry you lost a few days to this! I wish I had been able to help sooner. From my perspective- what you've described is working as expected, but I realize that it can be less than intuitive to someone new to this aspect of the library.

For what it's worth, I've tried to outline this in the docs. I mention pure components on the main docs page and recomputeRowHeights on the List docs page. If you have ideas for how this could be covered more clearly in the docs though- I welcome pull requests from the community. I'd love for you to contribute!

It sounds like you've worked this issue out for yourself in the meanwhile so I'm going to close it. We can talk more here if you have follow up questions though!

Was this page helpful?
0 / 5 - 0 ratings