petyosi / react-virtuoso

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

[BUG] Content blinks on scroll upward #1117

Closed nucleartux closed 1 month ago

nucleartux commented 1 month ago

Describe the bug I modified auto-prepend-items example to make items height dynamic. However, it's worth mentioning that prepend has nothing to do with this issue and can be deleted altogether. It was just very convenient example to modify.

Reproduction

import React from 'react'
import { Virtuoso } from '../src'
import { faker } from '@faker-js/faker'

function toggleBg(index: number) {
  return index % 2 ? 'var(--ifm-background-color)' : 'var(--ifm-color-emphasis-200)'
}

function user(index = 0) {
  const firstName = faker.name.firstName()
  const lastName = faker.name.lastName()

  return {
    index: index + 1,
    bgColor: toggleBg(index),
    name: `${firstName} ${lastName}`,
    initials: `${firstName.substr(0, 1)}${lastName.substr(0, 1)}`,
    jobTitle: faker.name.jobTitle(),
    description: faker.lorem.sentence(Math.floor(Math.random() * 100) + 10),
    longText: faker.lorem.paragraphs(1),
  }
}

const generated: ReturnType<typeof user>[] = []

const generateUsers = (length: number, startIndex = 0) => {
  return Array.from({ length }, (_, i) => getUser(i + startIndex))
}

const getUser = (index: number) => {
  if (!generated[index]) {
    generated[index] = user(index)
  }

  return generated[index]
}

export function Example() {
  const START_INDEX = 10000
  const INITIAL_ITEM_COUNT = 20

  const [firstItemIndex, setFirstItemIndex] = React.useState(START_INDEX)
  const [users, setUsers] = React.useState(() => generateUsers(INITIAL_ITEM_COUNT, START_INDEX))

  const prependItems = React.useCallback(() => {
    const usersToPrepend = 20
    const nextFirstItemIndex = firstItemIndex - usersToPrepend

    setTimeout(() => {
      setFirstItemIndex(() => nextFirstItemIndex)
      setUsers(() => [...generateUsers(usersToPrepend, nextFirstItemIndex), ...users])
    }, 5)

    return false
  }, [firstItemIndex, users, setUsers])

  return (
    <Virtuoso
      style={{ height: 500 }}
      components={{
        Header: () => <div style={{ textAlign: 'center', padding: '1rem' }}>Loading...</div>,
      }}
      firstItemIndex={firstItemIndex}
      initialTopMostItemIndex={INITIAL_ITEM_COUNT - 1}
      data={users}
      startReached={prependItems}
      itemContent={(_, user) => {
        return (
          <div style={{ backgroundColor: user.bgColor, padding: '1rem 0.5rem' }}>
            <h4>
              {user.index}. {user.name}
            </h4>
            <div style={{ marginTop: '1rem' }}>{user.description}</div>
          </div>
        )
      }}
    />
  )
}

To Reproduce Steps to reproduce the behavior:

  1. Open example
  2. Start to scroll upward
  3. See content blinks and content shift

Desktop (please complete the following information):

https://github.com/user-attachments/assets/a74e08fa-381a-44a7-a8dd-a3eab557eff7

petyosi commented 1 month ago

This is a duplicate of another report. It's basically a fight between this blink and the requestAnimationFrame in the resize observer.

petyosi commented 1 month ago

In v4.9.0 I've introduced an optional flag to skip the requestAnimationFrame delay. https://github.com/petyosi/react-virtuoso/commit/14e10f57d854fae05f721d840a9c63110bb0fa5c#diff-a0a3eb436be6f2828cb19615696181cb3bbdeb884582c69bfd841ca206ae2da9R260.

FWIW, I believe that this must be true by default, but it then turns on #1049 .