React-virtualized: VirtualScroll (List) with dynamic item height scrolling not smooth and jumping

Created on 30 Sep 2016  路  19Comments  路  Source: bvaughn/react-virtualized

I have been tuning the VirtualScroll (List) component for almost whole day but no luck.
I'm building a web based chatting application in which uses the react-virtualized List to display the chatting messages. Since message may have different content and different height, I use react-measure to calculate the item height and issue the recomputeRowHeights in rowRenderer.

The result is bad, VirtuallScroll List will jump around whenever I stopped the scrolling. For example, when I scrolled to the half of browser, I should see the middle of the messages, but it always suddenly shift the offset. Please take a look at the recorded video:
https://drive.google.com/file/d/0B_W64UoqloIkcm9oQ08xS09Zc1k/view?usp=sharing

Since I only use the List and Autosizer component, I only adapt the required css file into my project which is like

.VirtualScroll {
    width: 100%;
    outline: none;
}

For the render method, I nested a lot of flex components inside the rowRender:
Here is the code:

    render() {
        const inChat = this.context.store.getState().inChat;
        const {conversationList} = this.state;
        const imgUrl = 'img/builtin-wallpaper-1.jpg';
        const backgroundStyle = {
            backgroundImage: 'url(' + imgUrl + ')',
            backgroundRepeat: 'no-repeat',
            backgroundSize: 'cover',
            backgroundPosition: 'top left'
        };

        // console.log('inChat');
        // console.log(inChat);

        if (inChat.id === this.id && inChat.status === 'FETCHING'){
            return (
                <Box column center height="80%">
                    <CircularProgress />
                </Box>
            );
        } else if (inChat.id === this.id && inChat.status === 'FETCHED'){
            return (
                <Box column flex="1 0 100%" style={backgroundStyle}>
                    <HorizontalToolBar/>
                    <AutoSizer disableHeight={true}>
                        {({ width }) => (
                            <List
                                ref={(element) => {this.VirtualScroll = element;}}
                                className='VirtualScroll'
                                height={window.innerHeight - toolbarHeight - textAreaHeight}
                                overscanRowCount={10}
                                noRowsRenderer={this._noRowsRenderer.bind(this)}
                                rowCount={conversationList.length}
                                rowHeight={i => {
                                    return (Measured_Heights[i.index] | 20);  // default Height = 58
                                }}
                                rowRenderer={this._rowRenderer}
                                scrollToIndex={undefined} // scroll to latest item
                                width={width}
                            />
                        )}
                    </AutoSizer>
                    <InputControl chatId={this.id} sendChatText={this._sendChatText.bind(this)}/>
                </Box>
            );
        } else {
            return null;
        }
    }

    _rowRenderer ({ index, key, style, isScrolling }) {
        console.log(Measured_Heights);

        const rowData = this._getDatum(index);
        // let renderItem;

        // console.log('index = ' + index + ' key = ' + key);

        if (rowData.type == 'conversation') {

            if (rowData.data.type == netModule.TYPE_SYSTEM) {
                // system message
                return (
                    <Measure key={key} onMeasure={(dims) => this._onMeasure(index, dims)}>
                        <SystemMessage data={rowData.data}/>
                    </Measure>
                )
            }

            if (rowData.data.senderId == this.state.selfProfile.username) {
                // outgoing message
                return (
                    <Measure key={key} onMeasure={(dims) => this._onMeasure(index, dims)}>
                        <RightMessage
                            screenWidth={(window.innerWidth - leftToolBarWidth) / 2 }
                            screenHeight={window.innerHeight - toolbarHeight}
                            data={rowData.data}/>
                    </Measure>
                );

            } else {
                // incoming message
                // append userProfile to left messages
                return (
                    <Measure key={key} onMeasure={(dims) => this._onMeasure(index, dims)}>
                        <LeftMessage
                            userId={rowData.data.senderId}
                            userProfile={this.state.groupUserProfiles[rowData.data.senderId]}
                            screenWidth={(window.innerWidth - leftToolBarWidth) / 2 }
                            screenHeight={window.innerHeight - toolbarHeight}
                            data={rowData.data}/>
                    </Measure>
                );
            }
        }
    }

I read a couple docs that Flexbox may be intercept the scrolling event, but even though I added overflow-y: hidden to nested component I didn't see the issue disappear. Have you ever seen this wrong scrolling behavior with List component before? Any suggestion is welcome.

question

Most helpful comment

@EllisShen I had a similar symptom when upgrading a VirtualScroll to List (8.x). Make sure you pass the style property in _rowRenderer() to the parent components it returns (and that the components render() the style)

All 19 comments

Hi @EllisShen,

This looks like a pretty involved question. As the issues template suggests:


Are you asking a question?

_Please don't file Github issues to ask questions._

_Use Stack Overflow (http://stackoverflow.com/questions/tagged/react-virtualized) with a #react-virtualized tag or the react-virtualized Gitter chat room (https://gitter.im/bvaughn/react-virtualized)._


I'm the only maintainer for this project (and I happen to be on vacation for the next week) so I try to redirect questions to places where they'll be seen by more people than just me. 馃槃 Thanks for understanding.

@EllisShen I had a similar symptom when upgrading a VirtualScroll to List (8.x). Make sure you pass the style property in _rowRenderer() to the parent components it returns (and that the components render() the style)

Fantastic observation, @bhj. style doesn't appear to be used- and it's vital. Thanks!

@bhj Many thanks for your advice!! I tried and so far so good! Really appreciated.

@bvaughn Maybe we should add this important info into Doc?

@EllisShen It's in the docs. 馃槃 Looks like @bhj submitted PR #428 to try to make it a little more explicit. Feel free to share if you have specific suggestions for additional things I could do to improve the docs.

Whew! I was totally having this issue. The convention of requiring the style prop to be passed along to the component returned by rowRenderer didn't click until I found this issue tracker.

Part of my confusion was that I saw this same strange behavior when I had failed to properly import the CSS when using VirtualScroll in v7.

It might be helpful to give an example where you write a rowRenderer that simply wraps an existing element in a <div>, which makes it super obvious that the style is necessary for the component to render properly.

EDIT: Oh hey, this is almost exactly what you did with the example on the List documentation. It's worth noting that I didn't even find this documentation until I investigated the merge request referenced above. The example that shows up on the front page readme is comprehensive, but a little tough to follow because it's so full of state variables.

import 'SomeComponent' from 'SomeComponent.jsx';
import { List } from 'react-virtualized';

const VirtualListExample = ({ listObjects }) => {
  const render= ({ index, style }) => (
    <div style={{style}}>
      <SomeComponent someImportantProp={listObjects[index]} />
    </div>
  );

  return (
    <List
      width={200}
      height={400}
      rowCount={listObjects.length}
      rowRenderer={render}
      rowHeight={50}
    />
  );
};


Docs are hard 馃槄 The more I write, the harder it is to find any given one.

I try to provide a simplified use-case/example with each component that shows its most basic functionality. In the case of List the example would be here. I think the rowRenderer in that example fills the role you're describing?

Either way~ I happily welcome contributions to make the docs more user-friendly. I've been working with this library so long that I sometimes lose touch with common questions for people just starting out with it.

As noted above, I realized you'd posted a similar example just after I posted mine! Derp. If nothing else, maybe this conversation history will help the next person find their way there? :smile:

No problem! 馃槃 Please continue to share feedback and suggestions about the docs.

@baughn, sorry just saw your messages. I think I wasn't realized the Style was a critical key prop to making virtualscroll works properly. @bhj recent PR has greatly improved the doc, hopefully people won't encounter the same issue like me.

I think you meant @bvaughn.

On which note, bvaughn, congrats on finding an even more unique nickname, but I think mine sounds more name-like. If you wish, you may declare me your eternal nemesis.

Minor docs feedback -- the frontpage example for getting around issues with shallow-compare not detecting prop changes has this code snippet:

<List
  {...virtualScrollProps}
  sortBy={sortBy}
/>

Should this be changed to avoid reference to the old VirtualScroll component?

<List
  {...listProps}
  sortBy={sortBy}
/>

Oh no! Somebody summoned @Baughn, my arch nemesis! 馃槷 馃槀

@Nesciosquid: Reasonable docs suggestion. In the future, feel free to submit PRs with any such suggestions. I love getting docs-fixes 馃槃 I'll go ahead and make that update now though!

Not complaining, just offering one more data point: I too only discovered the "secret" style solution to the "jumping scrolling" issue through this thread (despite spending quite a long time going through the docs first).

Perhaps you could consider changing (on https://github.com/bvaughn/react-virtualized/blob/master/docs/Grid.md):

style        // Style object to be applied to cell (to position it)

(which makes it sound like a meaningless property you only use if you're too stupid to write your own CSS 馃槈) to instead be:

             // !!! REQUIRED !!! YOU MUST PASS THIS THROUGH !!!
style        //Style object to be applied to cell (to position it)

or:

style        // Style object to be applied to cell (to position it);
             // this must be passed through to the rendered cell element

Similarly a few lines down perhaps:

// Style is important since it specifies how the cell is to be sized and positioned.

could be:

// Style is important since it specifies how the cell is to be sized and positioned.
// If the style is not passed through you will have strange and inexplicable behavior
// (@see https://github.com/bvaughn/react-virtualized/issues/424)

or:

// Style must be passed through since it specifies how the cell is to be sized and positioned,
// and React Virtualized depends on this sizing/positioning.

Hey @machineghost. Fair suggestion. Would you like to submit a docs PR? 馃槃

Damn that was fast! If you care to weigh in on your preference for the verbiage (I was being a little flippant in my examples) I'd be happy to.

I prefer the second style props wording and probably the second further-down note as well. Not a strong preference on the further-down note though.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pkumar84 picture pkumar84  路  4Comments

miraage picture miraage  路  4Comments

iChip picture iChip  路  3Comments

johnnyji picture johnnyji  路  3Comments

bee0060 picture bee0060  路  3Comments