diff --git a/example/package.json b/example/package.json index d98ebef..ea0248f 100644 --- a/example/package.json +++ b/example/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@expo/vector-icons": "^13.0.0", - "@shopify/flash-list": "^1.4.2", + "@shopify/flash-list": "^1.5.0", "expo": "^48.0.0", "expo-constants": "~14.2.1", "expo-splash-screen": "~0.18.1", diff --git a/example/src/Shared/ExampleMasonry.tsx b/example/src/Shared/ExampleMasonry.tsx index 0be3ddd..260a65f 100644 --- a/example/src/Shared/ExampleMasonry.tsx +++ b/example/src/Shared/ExampleMasonry.tsx @@ -99,9 +99,20 @@ const ExampleMasonry: React.FC<{ limit?: number }> = ({ emptyList, nestedScrollEnabled, limit }) => { const [isRefreshing, startRefreshing] = useRefresh() + const [loading, setLoading] = React.useState(false) const [refreshing, setRefreshing] = React.useState(false) const [data, setData] = React.useState([]) + const loadmore = React.useCallback(async () => { + if (loading) { + return + } + setLoading(true) + const res = await asyncGetItems() + setLoading(false) + setData([...data, ...res]) + }, [loading, data]) + const refresh = React.useCallback(async () => { if (refreshing) { return @@ -129,6 +140,7 @@ const ExampleMasonry: React.FC<{ onRefresh={Platform.OS === 'ios' ? startRefreshing : undefined} refreshing={Platform.OS === 'ios' ? isRefreshing : undefined} nestedScrollEnabled={nestedScrollEnabled} + onEndReached={loadmore} /> ) } diff --git a/src/MasonryFlashList.tsx b/src/MasonryFlashList.tsx index e98bc70..7db0cc9 100644 --- a/src/MasonryFlashList.tsx +++ b/src/MasonryFlashList.tsx @@ -1,8 +1,5 @@ -import { - MasonryFlashListProps, - MasonryFlashList as SPMasonryFlashList, -} from '@shopify/flash-list' -import React from 'react' +import { MasonryFlashListProps, MasonryFlashListRef } from '@shopify/flash-list' +import React, { useCallback } from 'react' import Animated from 'react-native-reanimated' import { @@ -25,25 +22,39 @@ import { type MasonryFlashListMemoProps = React.PropsWithChildren< MasonryFlashListProps > -type MasonryFlashListMemoRef = typeof SPMasonryFlashList +type MasonryFlashListMemoRef = MasonryFlashListRef + +let AnimatedMasonry: React.ComponentClass< + MasonryFlashListProps +> | null = null + +const ensureMasonry = () => { + if (AnimatedMasonry) { + return + } + + try { + const flashListModule = require('@shopify/flash-list') + AnimatedMasonry = (Animated.createAnimatedComponent( + flashListModule.MasonryFlashList + ) as unknown) as React.ComponentClass> + } catch (error) { + console.error( + 'The optional dependency @shopify/flash-list is not installed. Please install it to use the FlashList component.' + ) + } +} const MasonryFlashListMemo = React.memo( React.forwardRef( (props, passRef) => { - // Load FlashList dynamically or print a friendly error message - try { - const flashListModule = require('@shopify/flash-list') - const AnimatedMasonryFlashList = (Animated.createAnimatedComponent( - flashListModule.MasonryFlashList - ) as unknown) as React.ComponentClass> + ensureMasonry() + return AnimatedMasonry ? ( // @ts-expect-error - return - } catch (error) { - console.error( - 'The optional dependency @shopify/flash-list is not installed. Please install it to use the FlashList component.' - ) - return <> - } + + ) : ( + <> + ) } ) ) @@ -60,6 +71,7 @@ function MasonryFlashListImpl( ) { const name = useTabNameContext() const { setRef, contentInset } = useTabsContext() + const recyclerRef = useSharedAnimatedRef(null) const ref = useSharedAnimatedRef(passRef) const { scrollHandler, enable } = useScrollHandlerY(name) @@ -74,8 +86,8 @@ function MasonryFlashListImpl( const { progressViewOffset, contentContainerStyle } = useCollapsibleStyle() React.useEffect(() => { - setRef(name, ref) - }, [name, ref, setRef]) + setRef(name, recyclerRef) + }, [name, recyclerRef, setRef]) const scrollContentSizeChange = useUpdateScrollViewContentSize({ name, @@ -117,20 +129,25 @@ function MasonryFlashListImpl( [_contentContainerStyle, contentContainerStyle.paddingTop] ) + const refWorkaround = useCallback( + (value: MasonryFlashListMemoRef | null): void => { + // https://github.com/Shopify/flash-list/blob/2d31530ed447a314ec5429754c7ce88dad8fd087/src/FlashList.tsx#L829 + // We are not accessing the right element or view of the Flashlist (recyclerlistview). So we need to give + // this ref the access to it + // @ts-expect-error + ;(recyclerRef as any)(value?.recyclerlistview_unsafe) + ;(ref as any)(value) + }, + [recyclerRef, ref] + ) + return ( // @ts-expect-error typescript complains about `unknown` in the memo, it should be T { - // https://github.com/Shopify/flash-list/blob/2d31530ed447a314ec5429754c7ce88dad8fd087/src/FlashList.tsx#L829 - // We are not accessing the right element or view of the Flashlist (recyclerlistview). So we need to give - // this ref the access to it - // eslint-ignore - // @ts-expect-error - ;(ref as any)(value?.recyclerlistview_unsafe) - }} + ref={refWorkaround} bouncesZoom={false} onScroll={scrollHandler} scrollEventThrottle={16}