necolas / react-native-web

Cross-platform React UI packages
https://necolas.github.io/react-native-web
MIT License
21.59k stars 1.78k forks source link

VirtualizedList - Viewability regression, seems affected by web implementation of Animated #2574

Open mtourj opened 1 year ago

mtourj commented 1 year ago

Is there an existing issue for this?

Edit: #2566 may be related

Describe the issue

Hello, I recently upgraded from 18.11 to 19.6, and I noticed that some of my components that use FlatList and rely on viewabilityConfig were no longer working properly. It seems onViewableItemsChanged stops working in some very specific cases. In the repro I provide, viewability breaks when I run Animated.loop on an animation that has useNativeDriver set to false, and after changing state on the data being rendered by the FlatList.

However, after fixing the issue by setting useNativeDriver: true on the animation that was causing it, I found that other areas of my app where also causing viewability to break even though I had no other instances of Animated.loop being called, so clearly this is not the only case that can cause this viewability issue to occur.

This is important for us because we rely on viewabilityConfig to determine when videos in a flatlist should stop/start playing.

Thanks for the great work bringing react native to the web!

Expected behavior

For onViewableItemsChanged to be called as expected, regardless of whether an animation is running in another part of the app

Steps to reproduce

  1. Create FlatList with viewability config props passed in:
    <FlatList
          data={data}
          horizontal
          renderItem={({ item, index: idx }) =>
            renderItem({
              item,
              index,
              isViewable: viewableItemIndices.includes(idx),
            })
          }
          viewabilityConfigCallbackPairs={
            viewabilityConfigCallbackPairs.current
          }
        />

    with the following values for viewabilityConfigCallbackPairs:

    
    const VIEWABILITY_CONFIG = {
    viewAreaCoveragePercentThreshold: 50,
    };

function MyComponent() { ...

```js
...
  const [viewableItemIndices, setViewableItemIndices] = useState<number[]>([]);

  const viewabilityConfigCallbackPairs = useRef([
    {
      viewabilityConfig: VIEWABILITY_CONFIG,
      onViewableItemsChanged: (info: {
        viewableItems: ViewToken[];
        changed: ViewToken[];
      }) => {
        console.log(
          'called',
          info.viewableItems.map((item) => item.index as number),
        );
        setViewableItemIndices(
          info.viewableItems.map((item) => item.index as number) || [],
        );
      },
    },
  ]);
  ...
  1. Start an animation loop with useNativeDriver: false

    useEffect(() => {
    animationLoop.current = Animated.timing(animationRef.current, {
      toValue: 2,
      duration: 1500,
      useNativeDriver: false
    });
    animationRef.current.setValue(0);
    Animated.loop(animationLoop.current).start();
    }, []);
  2. Make a state change on the data being rendered by FlatList, changing the length of the array:

    setData([...])
  3. Note how the console.log() call in onViewableItemsChanged is never reached

Test case

https://codesandbox.io/s/weird-virtualizedlist-2rc2ln?file=/src/App.js

Additional comments

I am not familiar with React Native internals, so I just hope this repro can point you to the root cause and help solve this problematic issue. Thank you!!

necolas commented 1 year ago

The web fork of Animated and VirtualizedList is no longer being maintained, but updating it is blocked on this React Native issue that will allow RN Web to install the same packages used in RN. You should comment there https://github.com/facebook/react-native/issues/35263