bvaughn / react-virtualized

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

Preserve list position on changed row content #1756

Open ChoppinBlockParty opened 2 years ago

ChoppinBlockParty commented 2 years ago

Feature Request

Struggle to make virtualized list scrolled to a particular row after content refresh. Scenario: there are few rows, user scrolls to somewhere in the list, and then triggers an event (e.g. presses a button in the app) that modifies the content of the list items. Ideally the user must see the same row at the top of the scroll view that was before the event occurred. See details below.

I could come up with two options: 1 recomputeRowHeights() should have a parameter that allows preserving view position during computations.

  1. list should have some kind of callback where client can get notified on complete rendering rows (onRowRenders gets called multiple time during remeasuring, so it does not suits). There must be correct method scrollToRow, the current one is flaky in case of measurements must be done, and triggers multiple row sections re-rendered, i.e. moves the view from the specified row.

Here is the snippet from my code where I try to make it work with no success.

class MyList extends React.Component {
  state = {
    newScrollToIndex: undefined
  }

  // function triggered from outsided events
  onCellContentChanged() {
    const index = this.listRowIndex

    // Don't know really which one to call exactlty
    // Just called everything
    this.cellMeasurerCache.clearAll()
    this.listView.recomputeRowHeights()
    this.listView.measureAllRows()

    /*
     * Tried this did not work at all.
     * const off = this.listView.getOffsetForRow({ index })
     * this.listView.scrollToPosition(off)
     */

    // This two seem to work equivalently with 50% chance to work correctly.
    // this.listView.scrollToRow(index)
    this.setState({ newScrollToIndex: index })
  }

  renderInfiniteList({ height, width }) {
    return (
      <InfiniteLoader
        isRowLoaded={this.isRowLoaded}
        loadMoreRows={this.loadMoreRows}
        rowCount={this.rowCount}
      >
        {({ onRowsRendered, registerChild }) => {
          return (
            <List
              style={{ outline: 'none' }}
              noRowsRenderer={() => (
                <NoRows
                  loading={
                    this.state.loadingMoreRows || this.state.loadingFields
                  }
                />
              )}
              height={height}
              width={width}
              overscanRowCount={2}
              rowCount={this.listViewRowCount}
              rowHeight={this.cellMeasurerCache.rowHeight}
              deferredMeasurementCache={this.cellMeasurerCache}
              rowRenderer={this.rowRenderer}
              scrollToIndex={this.state.newScrollToIndex}
              onRowsRendered={(o) => {
                onRowsRendered(o)
                this.listRowIndex = o.startIndex
              }}
              ref={(listView) => {
                registerChild(listView)
                this.listView = listView
              }}
            />
          )
        }}
      </InfiniteLoader>
    )
  }
}

In the current situation there is no way to preserve view position after content has been changed in the rows. Because recomputeRowHeights will call a re-render event that will trigger multiple onRowRendered callbacks and there is no way to know when re-computations are done and scrollToRow can be called, even then scrollToRow in itself triggers multiple row re-renders.

hmellahi commented 9 months ago

Hello @ChoppinBlockParty, I am having the same issue, did you manage to fix it?

johnnyshankman commented 6 months ago

+1 here having a similar issue where recomputing the row height by changing height={rowContainerHeight} causes the list to lose its position. can't come up with a solve because of the fact the onRowRendered fires off like crazy, as chopping described. there's no simple way to hold on to the last viewed row index and scroll back to it bc it fires off so many times.