import React, { useEffect } from "react"; import { type ViewProps } from "react-native"; import Animated, { cancelAnimation, useAnimatedStyle, useSharedValue, withRepeat, withSequence, withTiming, } from "react-native-reanimated"; import { useColorScheme } from "nativewind"; import { cn } from "../utils/cn"; interface SkeletonProps extends ViewProps { className?: string; } export const Skeleton = React.memo(function Skeleton({ className, ...props }: SkeletonProps) { const { colorScheme } = useColorScheme(); const isDark = colorScheme === "dark"; const opacity = useSharedValue(0.4); useEffect(() => { opacity.value = withRepeat( withSequence(withTiming(1, { duration: 750 }), withTiming(0.4, { duration: 750 })), -1 ); // Cancel the infinite repeat on unmount. Without this the worklet // keeps running until the shared value is GC'd, which in long // FlashList re-cycles (skeleton → loaded item → skeleton) leaves // orphan animations alive. return () => { cancelAnimation(opacity); }; }, [opacity]); const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value, })); return ( ); });