bvaughn / react-virtualized

React components for efficiently rendering large lists and tabular data
http://bvaughn.github.io/react-virtualized/
MIT License
26.39k stars 3.06k forks source link

Problem refreshing while filtering datas #1104

Closed basile-parent closed 1 month ago

basile-parent commented 6 years ago

Hello, I have a problem using the Grid component. I doing an iTunes like software and I'm trying to implement a search function. image

The datas are well filtered, the props are updated in all components but the rendering is very strange. If the result of the filtering return 3 rows (like in the below image), the grid render the first 3 rows of the initial data set and not taking into account the new data set. image

My code :

class TableContainer extends React.Component {

    render() {
        let filteredrowRenderers = [];
        if (this.state.rowRenderers) {
            filteredrowRenderers = this._getFilteredData();
        }

        return (
            <section id="list">
                <section id="searchData">
                    <FontIcon className="material-icons">search</FontIcon>
                    <TextField className="textField" name={"search"} placeholder={"Recherche"}
                               onKeyPress={ e => this._searchData(e.target.value) }
                    />
                </section>
                <MyVirtualizeTable headers={ this.headers }
                                 data={ filteredrowRenderers }/>
            </section>
        );
    }

    _getFilteredData() {
        const {rowRenderers, searchText} = this.state;

        if (searchText) {
            return rowRenderers.filter(rowRenderer => rowRenderer.data.searchText.indexOf(searchText.toLowerCase()) >= 0);
        }
        return rowRenderers;
    }

    _searchData(text) {
        this.setState({
            ...this.state,
            searchText: text
        });
    }

}

export default class MyVirtualizeTable extends React.PureComponent {

  ...

  render() {
    const rowCount = this.props.data.length;

    return (
      <div>
        <ScrollSync>
          {({
              clientHeight,
              clientWidth,
              onScroll,
              scrollHeight,
              scrollLeft,
              scrollTop,
              scrollWidth,
            }) => {
            return (
              <div ...>
                <AutoSizer>
                  {(childrenParam) => (
                    <div>
                      <div
                        <Grid
                          className={styles.HeaderGrid}
                          columnWidth={(index) => this._getColumnWidth(index, childrenParam.width - scrollbarSize())}
                          columnCount={columnCount}
                          height={rowHeight}
                          overscanColumnCount={overscanColumnCount}
                          cellRenderer={(headerParam) => this._renderHeaderCell(headerParam, childrenParam.width - scrollbarSize())}
                          rowHeight={rowHeight}
                          rowCount={1}
                          scrollLeft={scrollLeft}
                          width={childrenParam.width - scrollbarSize()}
                        />
                      </div>
                      <div>
                        <Grid
                          cellRenderer={(headerParam) => this._renderBodyCell(headerParam, childrenParam.width - scrollbarSize())}

                          className={styles.BodyGrid}
                          columnWidth={(index) => this._getColumnWidth(index, childrenParam.width - scrollbarSize())}
                          columnCount={columnCount}
                          height={childrenParam.height}
                          onScroll={onScroll}
                          overscanColumnCount={overscanColumnCount}
                          overscanRowCount={overscanRowCount}
                          rowHeight={rowHeight}
                          rowCount={rowCount}
                          width={childrenParam.width}
                        />
                      </div>
                    </div>
                  )}
                </AutoSizer>
              </div>
            );
          }}
        </ScrollSync>
      </div>
    );
  }

  _renderBodyCell({columnIndex, key, rowIndex, style}, totalWidth) {
    const rowRenderer = this.props.data[rowIndex];

    return (
      <div key={"body_" + key}>
        { rowRenderer.renderCell(columnIndex) }
      </div>
    );
  }

  _renderHeaderCell({columnIndex, key, rowIndex, style}, totalWidth) {
    const headerData = this.props.headers[columnIndex];

    return (
      <div key={"header_" + key}>
        { headerData.name }
      </div>
    );
  }

  ...

}
export class RowRenderer {

    ...

  renderCell = (column) => {

    switch (column) {
      case 0 :
        return (
          <div>
            { this.data.propA }
          </div>
        );
      case 1 :
        return (
          <div>
            { this.data.propB }
          </div>
        );
      case 2 :
        return (
          <div>
            { this.data.propC }
          </div>
        );
      ...
  };

}

Do you have any clue on why the grid is rendering its initial datas filtered on the n first rows an not the filtered datas its receives with props ?

Thank you.

pete-moss commented 6 years ago

In our use, if our data changes, we sometimes have to call forceUpdate() on the Grid instance to make it re-render. I think there are some optimizations in the Grid code to minimize the calls to render(). Simply reducing the rowCount may not be enough to trigger a rerender. This is even worse if you are using variable-height rows where you may have to call Grid.recomputeGridSize() after a data change.

Anderson-James-oss commented 6 years ago

How to use this scenario ? (forceUpdate())

I want to ask when to call forceUpdate() ? How to use it ? I'm not going to use forceUpdate()

export default class VirtTable extends React.Component {
    constructor(props){
        super(props);
    }

    _cellRenderer = ({columnIndex, key, rowIndex, style}) => {
       return (
           <div>{this.props.option.dataSource[rowIndex][columnIndex]}</div>
       );
     }

    componentDidUpdate(){
        //Do you use it here   
        // ....this.forceUpdate()
      }

    render(){
        return(
           <AutoSizer>
           {({ height, width }) => (
                 <MultiGrid
                 cellRenderer={this._cellRenderer}
                 columnWidth={146}
                 columnCount={this.props.option.dataSource[0].length}
                 fixedColumnCount={1}
                 fixedRowCount={0}
                 height={height}
                 rowHeight={60}
                 rowCount={this.props.option.dataSource.length}
                 width={width}
                 style={STYLE}
                 styleBottomLeftGrid={STYLE_BOTTOM_LEFT_GRID}
                 styleTopLeftGrid={STYLE_TOP_LEFT_GRID}
                 styleTopRightGrid={STYLE_TOP_RIGHT_GRID}
               />
               )}
           </AutoSizer>
        )
    }
}
pete-moss commented 6 years ago

In our component that wraps a MultiGrid (equivalent to your VirtTable), we added a dataFreshness: any prop. If you do that, the user of VirtTable would then change this value whenever the data changes. Then in the componentWillReceiveProps() lifecycle method, check to see if dataFreshness has changed. If so, call MultiGrid.forceUpdateGrids(). You will have to get a ref to the MultiGrid in order to call this method. That forces a rerender pass on all Grids contained by the MultiGrid. That should ensure that your new rows are rendered. This technique works great when we are fetching new data and doing sorting and filtering since the rowCount may or may not change.

wuweiweiwu commented 6 years ago

I would try to update to 9.19.x version of react virtualized. any prop changes should trigger a re-render.

however, forceUpdate and forceUpdateGrids are also viable alternatives.

hope that helps!

wuweiweiwu commented 6 years ago

@koalajun I would recommend calling forceUpdate when you data updates.

ex:

fetch('/endpoint')
  .then(data => data.json())
  .then(data => {
     this.setState({ gridProps: data }, () => this.gridRef.forceUpdate());
   })
krayush commented 6 years ago

@wuweiweiwu: Didn't work. I have a similar issue working with List component and the input data is filtered by a outer component.

Used both forceUpdate and forceUpdateGrids, none of them worked

kpennell commented 6 years ago

Having similar problems with client side filtering. All of this has forced me to learn this api well. But yeah, still can't figure out how to update this.

krayush commented 6 years ago

@kpennell: Share the sample code here. I was able to fix this using rowRenderer function. Inside the rowRenderer function, we need to use the filtered data only.

In componentDidUpdate:

componentDidUpdate() {
        this.List.current.forceUpdate();
        this.List.current.forceUpdateGrid();
}
kpennell commented 6 years ago

@krayush thx for this. I'll give it a try.

priley86 commented 5 years ago

Hi, I'm seeing a similar issue when data changes after I have set isScrollingOptOut = true. My current workaround is to refresh the grid onComponentUpdate:

https://github.com/openshift/console/blob/5107eacc97430f453dc9cf573d0f779dd5eea746/frontend/public/components/factory/table.tsx#L365

We've written a wrapper around VirtualGrid due to changes proposed in #1377, however this is essentially the same:

 this._cellMeasurementCache.clearAll();
 this._gridRef.recomputeGridSize();