showtime-xyz / showtime-tab-view

A react native TabView component that support collapse header and custom refresh control, powered by Reanimated & GestureHandler. Join Showtime:
https://showtime-xyz.notion.site/Join-Showtime-Public-fa6282938e284134b302184062d7e329
MIT License
219 stars 23 forks source link

Blank `FlashList` after refresh and switch between tab #20

Open kirillzyusko opened 7 months ago

kirillzyusko commented 7 months ago

Steps to reproduce

Actual behavior

Content is not visible.

Expected behavior

Content should be visible.

Code to reproduce

import React, { useCallback, useState } from "react";
import { StatusBar, Text, View } from "react-native";
import { useSharedValue } from "react-native-reanimated";
import { Route, TabView } from "@showtime-xyz/tab-view";
import { TabFlashList } from "@features/Social/components/TabFlashList";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { TabFlashList } from "./tab-flash-list";

const StatusBarHeight = StatusBar.currentHeight ?? 0;
const TabScene = ({ route }: any) => {
  return (
    <TabFlashList
      index={route.index}
      data={new Array(60).fill(0)}
      estimatedItemSize={79}
      renderItem={({ index }) => {
        return (
          <View
            style={{
              height: 71,
              backgroundColor: "#fff",
              marginBottom: 8,
              justifyContent: "center",
              alignItems: "center",
            }}
          >
            <Text>{`${route.title}-Item-${index}`}</Text>
          </View>
        );
      }}
    />
  );
};

export default function Example() {
  const [isRefreshing, setIsRefreshing] = useState(false);
  const [routes] = useState<Route[]>([
    { key: "like", title: "Like", index: 0 },
    { key: "owner", title: "Owner", index: 1 },
    { key: "created", title: "Created", index: 2 },
  ]);
  const [index, setIndex] = useState(0);
  const animationHeaderPosition = useSharedValue(0);
  const animationHeaderHeight = useSharedValue(0);

  const renderScene = useCallback(({ route }: any) => {
    switch (route.key) {
      case "like":
        return <TabScene route={route} index={0} />;
      case "owner":
        return <TabScene route={route} index={1} />;
      case "created":
        return <TabScene route={route} index={2} />;
      default:
        return null;
    }
  }, []);

  const onStartRefresh = async () => {
    setIsRefreshing(true);
    setTimeout(() => {
      console.log("onStartRefresh");
      setIsRefreshing(false);
    }, 300);
  };
  const renderHeader = () => (
    <View style={{ height: 300, backgroundColor: "#000" }}></View>
  );
  return (
    <GestureHandlerRootView style={{flex: 1}}>
      <TabView
        onStartRefresh={onStartRefresh}
        isRefreshing={isRefreshing}
        navigationState={{ index, routes }}
        renderScene={renderScene}
        onIndexChange={setIndex}
        lazy
        renderScrollHeader={renderHeader}
        minHeaderHeight={44 + StatusBarHeight}
        animationHeaderPosition={animationHeaderPosition}
        animationHeaderHeight={animationHeaderHeight}
      />
    </GestureHandlerRootView>
  );
}

Screenshots:

https://github.com/showtime-xyz/showtime-tab-view/assets/22820318/a40e8486-f918-4c46-931e-032236db261b

Environment info:

kirillzyusko commented 6 months ago

The problem was fixed by these changes:

     onScroll: (e) => {
       const moveY = e.contentOffset.y;
       scrollY.value = moveY;
-      if (curIndexValue.value !== index) return;
-      shareAnimatedValue.value = moveY;
       if (propOnScroll) {
         runOnJS(propOnScroll as any)({ nativeEvent: e });
       }
+      if (curIndexValue.value !== index) return;
+      shareAnimatedValue.value = moveY;
     },
   });
   // adjust the scene size

It seems like runOnJS(propOnScroll as any)({ nativeEvent: e }); wasn't called when the scene was not in focus. So literally we were scrolling the scene to the initial position (scrollTo from REA), but we didn't propagate this event to JS. As a result FlashList had old offset and was rendering incorrect elements.

Moving runOnJS(propOnScroll as any)({ nativeEvent: e }); before if (curIndexValue.value !== index) return; seems to resolve this problem 👀

alantoa commented 4 months ago

Hey, @kirillzyusko, sorry for replying late. I'm not actively maintaining this library because I find the current implementation messy. I always think about how to make this library run perfectly and be easy to use instead of passing an index to the scene view. Also, I have left Showtime. Next, I'm planning to create a new library and refactor it to make it more composable. And this time I will no longer use react-native-tab-view. I will let you know when it's ready.