petyosi / react-virtuoso

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

[BUG] endless scroll not align while data loaded #1119

Closed ZowieTao closed 3 months ago

ZowieTao commented 3 months ago

Describe the bug When using Virtuoso to load new data while scrolling upwards, the newly loaded data causes the list to jump to the top instead of maintaining the scroll position.

Reproduction Use CodeSandbox to illustrate the problem so that I can observe the issue on your side and ensure that a potential fix reliably addresses it.

To Reproduce Steps to reproduce the behavior:

  1. Implement a Virtuoso component with upwards scrolling data loading.
  2. Scroll up to trigger data loading.
  3. Observe that the list jumps to the top after new data is loaded.

Expected behavior The list should maintain its scroll position after loading new data when scrolling upwards.

Screenshots 20240805005051_rec_

Additional context

function App() {
  const [users, setUsers] = useState(() => [])

  const loadMore = useCallback(() => {
    return setTimeout(() => {
      setUsers((users) => [...generateUsers(20, 10e4 - users.length), ...users])
    }, 500)
  }, [setUsers])

  useEffect(() => {
    const timeout = loadMore()
    return () => clearTimeout(timeout)
  }, [])

  return (
    <Virtuoso
      style={{ height: 300 }}
      data={users}
      followOutput
      alignToBottom
      atTopStateChange={loadMore}
      increaseViewportBy={200}
      firstItemIndex={10e4}
      initialTopMostItemIndex={users.length - 1}
      itemContent={(index, user) => {
        return <div>{user.name}</div>
      }}
      components={{ Header: Footer }}
    />
  )
}

const Footer = () => {
  return (
    <div
      style={{
        padding: '2rem',
        display: 'flex',
        justifyContent: 'center',
      }}
    >
      Loading...
    </div>
  )
}

function generateUsers(count, start) {
  return Array.from({ length: count }, (_, i) => ({
    name: `Msg: ${start - count + i}`,
  }))
}

render(<App />)
petyosi commented 3 months ago

The example you've posted does not implement prepending correctly; it's not your fault really, prop-based prepending mechanism is fairly convoluted. If you're going to build a reverse scrolling I strongly recommend the MessageList. It handles data prepend much better.

https://virtuoso.dev/virtuoso-message-list/working-with-data/#prepending-data