react-virtualized: Do you have any plans for making a two-way infinitely scrollable view?

There are several things I’d like to make infinitely scrollable:

  • Calendars
  • Lists of documents sorted by date

In both of these cases I want to be able to scroll as far into the past or future as possible, but since <Grid> isn’t really designed to scroll up above row 0, it would take some kind of hacks to enable that.

For use cases like this, I think it’s probably better for the scroll container to wrap around when it reaches the bottom or top, but maintain a “virtual” scroll position. Then the scroll container could pass its virtual scroll bounds (top/bottom/left/right) to a function I provide that decides what to render for those bounds.

But maybe that should just be a separate package…what do you think?

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 22 (20 by maintainers)

Most upvoted comments

@jedwards1211 For what it’s worth, you could check out what the Angular Material team came up with for inspiration, sounds quite similar to what you’re describing. If I recall correctly, it works by listening to mousewheel events and translating a container with translate3d, and can “scroll” infinitely in both directions. Check out this infinite scrolling datepicker for reference: https://material.angularjs.org/1.1.2/demo/datepicker

It doesn’t feel quite as perfect as native scrolling, but it’s pretty close. The differences are mostly noticeable when scrolling really fast.

@clauderic ah I see, yeah, that’s a good point that it’s usually no problem to restrict the min date. Well it’s certainly possible to add a way to jump to a given year and month without sacrificing the infinite scrolling in a datepicker. My initial inspiration for this came from the macOS calendar, which I think is pretty nice. (Interestingly, it treats slow scrolling and fast flicks as separate gestures, for the latter it aligns to the prev/next month.)

@clauderic you didn’t even shill your own react-infinite-calendar? Looks awesome! Don’t be so humble 😉

Oh, that sucks…

I got a basic two-way vertical infinite scroll wrapper for Grid working in Chrome! Notably, I had to call the Grid’s _resetStyleCache whenever scrolling wraps around to make the cells render the proper contents before scrolling stops.

Here’s the code:

import React from 'react'
import ReactDOM from 'react-dom'
import {Grid} from 'react-virtualized'

export default class InfiniteGrid extends React.Component {
  state = {
    scrollTop: 0,
    yOffset: 0,
  }

  onScroll = ({clientHeight, scrollHeight, scrollTop}) => {
    if (scrollTop >= scrollHeight - clientHeight) {
      const newScrollTop = scrollTop % this.props.rowHeight + 1
      this.setState({
        scrollTop: newScrollTop,
        yOffset: this.state.yOffset + scrollHeight - clientHeight - newScrollTop,
      })
      if (this.grid) this.grid._resetStyleCache()
    } else if (scrollTop == 0) {
      this.setState({
        scrollTop: scrollHeight - clientHeight - 1,
        yOffset: this.state.yOffset - scrollHeight + clientHeight + 1,
      })
      if (this.grid) this.grid._resetStyleCache()
    } else {
      this.setState({scrollTop})
    }
  }

  cellRenderer = (props) => {
    const {cellRenderer, rowHeight} = this.props
    const {yOffset} = this.state
    const rowIndex = props.rowIndex + Math.floor(yOffset / rowHeight)
    return cellRenderer({...props, rowIndex})
  }

  render(): React.Element<any> {
    const {height, rowHeight} = this.props
    const {scrollTop, yOffset} = this.state
    const rowCount = Math.ceil(height / rowHeight) + 10
    return (
      <Grid
          {...this.props}
          ref={c => this.grid = c}
          yOffset={yOffset}
          rowCount={rowCount}
          scrollTop={scrollTop}
          counter={scrollTop}
          onScroll={this.onScroll}
          cellRenderer={this.cellRenderer}
      />
    )
  }
}

ReactDOM.render(
    <InfiniteGrid
        columnCount={2}
        width={600}
        height={600}
        columnWidth={300}
        rowHeight={30}
        cellRenderer={({rowIndex, columnIndex, key, style}) =>
          <div key={key} style={style}>
            {rowIndex}, {columnIndex}
          </div>
        }
    />,
    document.getElementById('root')
)