React-virtualized: Scroll to index

Created on 16 Dec 2016  路  9Comments  路  Source: bvaughn/react-virtualized

Feature request

I have a list with a button that scrolls to the top of the list. The user can press the button any time, and when he does, the list should scroll to the top. The button may be pressed multiple times, the user may scroll in between. Each time, the list should scroll to the top.

The current workaround is to use the scrollToRow prop, and set it to 0 when the button is pressed. You then have to spy on the scroll position, and change the scrollToRow prop to undefined when the component has reached the correct scroll position.

I'm suggesting having a scrollToRow method instead, which would allow the following use case :

class Weeks extends Component {
  constructor(props) {
    super(props)
    auto_bind(this)
  }

  scroll_to_top() {
    this.list.scrollToRow(0)
  }

  render_week({key, index, style}) {
    return (
      <div key={key} style={style}>
        <Week week={index}/>
      </div>
    )
  }

  render() {
    return (
      <List
        height={height}
        width={width}
        ref={(list) => {
          this.list = list
        }}
        rowCount={100}
        rowHeight={row_height}
        rowRenderer={this.render_week}
        />
    )
  }
}

This is the same strategy as the React Native ScrollView component : https://facebook.github.io/react-native/docs/scrollview.html#scrollview

I can implement it if you give me the go-ahead.

(This issue is linked to this stack overflow question : http://stackoverflow.com/questions/41122140/how-to-scroll-to-index-in-infinite-list-with-react-virtualized/41126137?noredirect=1#comment69546597_41126137)

Most helpful comment

is there a way to scroll a specific row by index number to the top of the scrollable area?

All 9 comments

About the pure component concern, I want to argue that what pure components are advocates for my solution. If indeed a component is the reflection of props, then setting a "scrollToRow" prop implies that the component is scrolling to this row whenever it is rendered, which doesn't make sense. The user would see the component scrolling back to this row seamingly without any reason.

Therefore, what your are doing at the moment is ignoring the prop when it isn't changed. You're not saying "this prop is the same as before, so I can use my cached result instead of rerendering", you're saying "it looks like the user didn't notice that my internal source of truth drifted from his source of truth, so i will chose to ignore this time, not really knowing if the user intentionally sent me this prop or if it's simply to difficult for him to know if my source of truth drifted from it".

You seem to think that relying on "refs" is a proof of bad design, but I really don't agree. I can provide many examples where it is fully accepted design. Any component which needs an internal state that it can update very quickly without rerendering everything is a good fit : a text input, a scroll area, a drag and drop area ... The "best" design (if we want to get rid of all refs and only have state go down) would be to have scrollTop as a prop declaring the current scroll position and scrollTo as a prop called whenever the user scrolls so that the parent can have the opportunity to rerender with the correct scrollTop prop. But for performance reasons, you shouldn't do this of course. You should let the scroll component handle its own scroll position.

Except sometimes it make sense for the parent to ask its child to set its state to a particular value. That's why a ref with a method makes sense here.

I agree with you that you shouldn't have two ways of doing the same thing, and if I was making the decision, I would remove the scrollToRow altogether. I guess the reasonable thing to do is simply deprecating it for the time being.

Just so we can compare our two versions, I'm posting here your current suggestion as a workaround for this :

class Weeks extends Component {
  constructor(props) {
    super(props)
    this.state = {
      scrolling_to_week: null,
    }
    auto_bind(this)
  }

  scroll_to_top() {
    this.setState({scrolling_to_week: 0})
  }

  handle_scroll({scrollTop}) {
    if (this.state.scrolling_to_week != null && scrollTop == row_height * this.scrolling_to_week) {
      this.setState({scrolling_to_week: null})
    }
  }

  render_week({key, index, style}) {
    return (
      <div key={key} style={style}>
        <Week week={index}/>
      </div>
    )
  }

  render() {
    return (
      <List
        height={height}
        width={width}
        rowCount={100}
        rowHeight={row_height}
        rowRenderer={this.render_week}
        scrollToRow={this.state.scrolling_to_week}
        onScroll={this.handle_scroll}
        />
    )
  }
}

The tone of this issue seems kind of aggressive, or at least that's the way I'm reading it. Can we step it down a notch?

I understand and (mostly) agree with your concerns about the props. All 4 of the scroll-to-* props are a little awkward in that regard currently. I evaluated pros and cons of the API 8 or so months ago, whenever I built that feature, and I thought I chose the one that was simplest to use for most cases. Looking back, I might not make the same choice now.

You seem to think that relying on "refs" is a proof of bad design, but I really don't agree.

I don't think that. What I said was:

Ideally React components should be controlled through props. API methods require refs and are kind of awkward to work with by comparison.

Refs are fine. They're used in a lot of cases with react-virtualized. They can be a bit awkward to work with for people who are new to React though, or just in general- as they require the use of stateful components- so I don't like _requiring_ people to use them.

That being said, one solution in this case could be to rename the scroll-to-* props to be default-scroll-to-* to allow them to be set once, initially, when a component is created. Changes could then be done through API methods for people who want to use them.

That makes other use-cases more awkward though. For example, if scrollToRow changes _along with_ some other props- you would have to call the scroll method _after_ the props were set, since new props could change size/position info and have impact on the scroll position. This will necessitate an additional render though.

Sorry if I seemed aggressive, that was not what I meant at all ^^ I just tend to be passionate about things I believe in 馃槆I really love react-virtualized, and I looked for such a library for a long time, I couldn't belive nothing of the kind existed. I highly rely on it, and that's why I'm really interested in this feature being present, because there is nothing like react-virtualized out there that I can turn to.

Having props to define the initial state seems to be a general pattern, ListView in React Native does the same, so it looks like a safe route to me.

React team also suggests another pattern with the input component : controlled vs. uncontrolled components : https://facebook.github.io/react/docs/forms.html and https://facebook.github.io/react/docs/uncontrolled-components.html

I don't think it makes total sense here, but I just wanted to bring it up. You are concerned about what should happen with the scroll position when size / position changes : the whole problem comes from the fact that it's an uncontrolled component, but we would like to have the fine tuning level of a controlled component. Do you agree with my analysis ?

No worries! Thanks for clarifying intent. 馃槃

You're right, it is a question of controlled vs uncontrolled. However I tend to think scrolling is unique in this category:

  • It is highly optimized by browsers and managed in a separate thread
  • It changes _very_ frequently, unlike input controls

I think it would be awkward for an component like List or Grid to expose scrolling as a controlled prop (and it might be bad for perf too) _but_ the convenience of allowing users to set it as a prop is often quite nice. And so we end up where we are.

Maybe a change in name of the property would help clarify its usage (eg passiveScrollToRow or something). I don't know.

Released in 8.11.0

Thank you so much!! This is awesome :D

is there a way to scroll a specific row by index number to the top of the scrollable area?

@kelly-tock Yes (docs):

<List
  scrollToIndex={index}
  scrollToAlignment="start"
  ...
/>
Was this page helpful?
0 / 5 - 0 ratings

Related issues

iChip picture iChip  路  3Comments

johnnyji picture johnnyji  路  3Comments

davidychow87 picture davidychow87  路  3Comments

ms007 picture ms007  路  4Comments

zllc picture zllc  路  3Comments