Skip to content

Commit

Permalink
Merge pull request #360 from gkartalis/gkartalis/fix-masonry-recycling
Browse files Browse the repository at this point in the history
  • Loading branch information
andreialecu authored Aug 10, 2023
2 parents f96760b + d799eeb commit e6a53b3
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 30 deletions.
2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 12 additions & 0 deletions example/src/Shared/ExampleMasonry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item[]>([])

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
Expand Down Expand Up @@ -129,6 +140,7 @@ const ExampleMasonry: React.FC<{
onRefresh={Platform.OS === 'ios' ? startRefreshing : undefined}
refreshing={Platform.OS === 'ios' ? isRefreshing : undefined}
nestedScrollEnabled={nestedScrollEnabled}
onEndReached={loadmore}
/>
)
}
Expand Down
75 changes: 46 additions & 29 deletions src/MasonryFlashList.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -25,25 +22,39 @@ import {
type MasonryFlashListMemoProps = React.PropsWithChildren<
MasonryFlashListProps<unknown>
>
type MasonryFlashListMemoRef = typeof SPMasonryFlashList
type MasonryFlashListMemoRef = MasonryFlashListRef<any>

let AnimatedMasonry: React.ComponentClass<
MasonryFlashListProps<any>
> | null = null

const ensureMasonry = () => {
if (AnimatedMasonry) {
return
}

try {
const flashListModule = require('@shopify/flash-list')
AnimatedMasonry = (Animated.createAnimatedComponent(
flashListModule.MasonryFlashList
) as unknown) as React.ComponentClass<MasonryFlashListProps<any>>
} 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<MasonryFlashListMemoRef, MasonryFlashListMemoProps>(
(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<MasonryFlashListProps<any>>
ensureMasonry()
return AnimatedMasonry ? (
// @ts-expect-error
return <AnimatedMasonryFlashList ref={passRef} {...props} />
} catch (error) {
console.error(
'The optional dependency @shopify/flash-list is not installed. Please install it to use the FlashList component.'
)
return <></>
}
<AnimatedMasonry ref={passRef} {...props} />
) : (
<></>
)
}
)
)
Expand All @@ -60,6 +71,7 @@ function MasonryFlashListImpl<R>(
) {
const name = useTabNameContext()
const { setRef, contentInset } = useTabsContext()
const recyclerRef = useSharedAnimatedRef<any>(null)
const ref = useSharedAnimatedRef<any>(passRef)

const { scrollHandler, enable } = useScrollHandlerY(name)
Expand All @@ -74,8 +86,8 @@ function MasonryFlashListImpl<R>(
const { progressViewOffset, contentContainerStyle } = useCollapsibleStyle()

React.useEffect(() => {
setRef(name, ref)
}, [name, ref, setRef])
setRef(name, recyclerRef)
}, [name, recyclerRef, setRef])

const scrollContentSizeChange = useUpdateScrollViewContentSize({
name,
Expand Down Expand Up @@ -117,20 +129,25 @@ function MasonryFlashListImpl<R>(
[_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
<MasonryFlashListMemo
{...rest}
onLayout={onLayout}
contentContainerStyle={memoContentContainerStyle}
ref={(value) => {
// 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}
Expand Down

0 comments on commit e6a53b3

Please sign in to comment.