petyosi / react-virtuoso

The most powerful virtual list component for React
https://virtuoso.dev
MIT License
5.23k stars 300 forks source link

followOutput not working with fast updates #317

Closed wahlstedt closed 3 years ago

wahlstedt commented 3 years ago

I was excited to learn about this library and set about trying to replace an existing chat solution, using a pretty basic Virtuoso container:

<Virtuoso
   data={props.messages}
   itemContent={itemContent}
   initialTopMostItemIndex={props.messages.length - 1}
   alignToBottom
   followOutput={true}
/>

It works like expected most of the time but when several new text items are added at once the textbox will not autoscroll until the user manually scrolls down to the bottom again. (Just as if the user had scrolled back.)

This is 100% reproducible in my current project, but when I tried to make a showcase where I did pretty much the same thing with adding multiple items at once, it works as expected! So it must be something more complex going on.

I've tried reducing the existing text html to simple unstyled divs and the problem still occurs, so it doesn't seem to be related to the css. It also happens in all browsers tested (Chrome, Safari, iOS Safari).

I did a test where I delayed problematic client-side added texts to be added after 1 second instead of immediately, and that did make it work again, so it sure seems related to the speed at which new items are added.

If I set followOutput to a function that always returns true it also works as expected.

Any ideas what might be causing this? I had a look at the source to try to see how it checks if the text is scrolled to the bottom and saw that the state manager has something called debounceTime Could this cause problems when the html render hasn't finished updating the layout before new data comes in?

petyosi commented 3 years ago

Hey,

can you please provide a reproduction? I will re-open the ticket once I have such.

wahlstedt commented 3 years ago

I made a sandbox version of the same basic principle but the issue doesn't happen there: https://codesandbox.io/s/react-virtuoso-v1-chat-forked-bz9y

I sent you a pm with links to the full test project.

I made screen recordings of how it looks, (the text in red is added client-side and the rest is regular chat text) This is setting followOutput to always return true in order to always force a scroll:

https://user-images.githubusercontent.com/1794720/111304449-0985b880-8656-11eb-8758-469680d9f334.mov

...and this is when followOutput=true:

https://user-images.githubusercontent.com/1794720/111304299-df33fb00-8655-11eb-82d5-951b287e516a.mov

wahlstedt commented 3 years ago

I experimented with the source and found that removing the offsetBottom === 0 check at the following line removes the problem in my case: https://github.com/petyosi/react-virtuoso/blob/f220f06547ad73009f898c55162008b6c76f1fb9/src/stateFlagsSystem.ts#L75

Could this be a clue to what is happening?

wahlstedt commented 3 years ago

Another observation that might be related; with certain font sizes (and not with others) the text will start to flicker when there's a single item displayed. It seems to be caused by the He component constantly re-rendering..?

Example:

https://user-images.githubusercontent.com/1794720/112310348-c4d8cd80-8ca4-11eb-97df-72dd56bf5008.mov

Activating Chrome's paint flashing shows that it keeps re-rendering when there's 3 items, but not 2...

https://user-images.githubusercontent.com/1794720/112311882-788e8d00-8ca6-11eb-8666-a46c8bcb9e11.mov

During the flickering, the data-known-size for the last item keeps toggling between two values:

https://user-images.githubusercontent.com/1794720/112313250-ef785580-8ca7-11eb-9a5a-79a137e33490.mov

petyosi commented 3 years ago

I am unable to help you unless you create a clear, simple reproduction in a codesandbox environment that reduces the problem to the virtuoso component.

wahlstedt commented 3 years ago

Understandable. I've tried to isolate it into a sandbox, but my app is complex and asynchronous so I haven't succeeded to replicate the issue there. I was hoping to maybe get some pointers to what might be the cause in case I've implemented it wrong. Like what could cause the data-known-size to change like that, or how offsetBottom is calculated.

The troubleshooting section in the docs tells you not to use margins in the items. Maybe there's some other not mentioned caveat that I need to adhere to?

OrcaXS commented 3 years ago

I've met a similar problem. When a new item is added before the previous scrolling completes, scrolling will stop there and no longer following the bottom.

I've borrowed some code from the repo documents to make a reproduction. When appendInterval is 1000 and user.description's length is 10 everything runs fine. Decreasing the interval to 500 while increasing user.description's length to 2000 to make the list grows faster than scrolling, the list stops sticking bottom.

https://codesandbox.io/s/adoring-platform-1slp6

petyosi commented 3 years ago

If you expect fast updates, use "auto" as scroll behavior.

pranas commented 3 years ago

I believe we are also experiencing the same issue. However, the flickering seems to only appear with two conditions: 1) the list cannot be full and needs to have padding 2) you have to adjust the browser zoom level to a certain level (typically out like 67%-90%). I will try to create a reproduction example. @petyosi did you see issues with browser zoom before? Could this be accounted for in calculations and fixed?

petyosi commented 3 years ago

@pranas As far as I have debugged, browser zoom is a mess. Incorrect measures are reported. Feel free to debug further. There's no need to keep the conversation in this thread. You can open a new one with a clear reproduction (or, even, a PR).

wahlstedt commented 2 years ago

I still have this issue with the current version (2.16.1) but stumbled on a fix that I wanted to add here in case others encounter the same problem.

What fixed it for me was twofold: add the atBottomThreshold prop and set it to at least 6, AND change the css for my rendered data items to have a padding-bottom of 6px, upped from 1px before.

These 2 changes combined, made things work for me (on Chrome 103, MacOS)