Simplify list logic further to prevent misuse (#3334)

* simplify list logic further

more simplification

simplify by removing `isEmpty`

use `isFetchingNextPage` everywhere for clarity

change `isFetching` to `isFetchingNextPage` for clarity

remove some useless `useMemo`s

move `renderItem` and `keyExtractor` out of component

* clean bundle size check

* update deploy

* adjust

* adjust

* one test

* try now

* test it

* done
This commit is contained in:
Hailey 2024-04-03 20:59:33 -07:00 committed by GitHub
parent b1bd7ab6e3
commit 8e393b16f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 241 additions and 258 deletions

View file

@ -1,9 +1,9 @@
import React from 'react'
import {View} from 'react-native'
import {useNavigation} from '@react-navigation/core'
import {StackActions} from '@react-navigation/native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/core'
import {StackActions} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types'
import {CenteredView} from 'view/com/util/Views'

View file

@ -1,47 +1,54 @@
import React from 'react'
import {View} from 'react-native'
import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
import {Trans} from '@lingui/macro'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {List} from '#/view/com/util/List'
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {useLikedByQuery} from '#/state/queries/post-liked-by'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
import {ListFooter} from '#/components/Lists'
import {cleanError} from 'lib/strings/errors'
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
import {List} from '#/view/com/util/List'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {atoms as a, useTheme} from '#/alf'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
function renderItem({item}: {item: GetLikes.Like}) {
return <ProfileCardWithFollowBtn key={item.actor.did} profile={item.actor} />
}
function keyExtractor(item: GetLikes.Like) {
return item.actor.did
}
export function LikedByList({uri}: {uri: string}) {
const t = useTheme()
const {_} = useLingui()
const initialNumToRender = useInitialNumToRender()
const [isPTRing, setIsPTRing] = React.useState(false)
const {
data: resolvedUri,
error: resolveError,
isFetching: isFetchingResolvedUri,
isLoading: isUriLoading,
} = useResolveUriQuery(uri)
const {
data,
isFetching,
isFetched,
isRefetching,
isLoading: isLikedByLoading,
isFetchingNextPage,
hasNextPage,
fetchNextPage,
isError,
error: likedByError,
refetch,
} = useLikedByQuery(resolvedUri?.uri)
const error = resolveError || likedByError
const isError = !!resolveError || !!likedByError
const likes = React.useMemo(() => {
if (data?.pages) {
return data.pages.flatMap(page => page.likes)
}
return []
}, [data])
const initialNumToRender = useInitialNumToRender()
const error = resolveError || likedByError
const onRefresh = React.useCallback(async () => {
setIsPTRing(true)
@ -54,56 +61,47 @@ export function LikedByList({uri}: {uri: string}) {
}, [refetch, setIsPTRing])
const onEndReached = React.useCallback(async () => {
if (isFetching || !hasNextPage || isError) return
if (isFetchingNextPage || !hasNextPage || isError) return
try {
await fetchNextPage()
} catch (err) {
logger.error('Failed to load more likes', {message: err})
}
}, [isFetching, hasNextPage, isError, fetchNextPage])
}, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
const renderItem = React.useCallback(({item}: {item: GetLikes.Like}) => {
if (likes.length < 1) {
return (
<ProfileCardWithFollowBtn key={item.actor.did} profile={item.actor} />
)
}, [])
if (isFetchingResolvedUri || !isFetched) {
return (
<View style={[a.w_full, a.align_center, a.p_lg]}>
<Loader size="xl" />
</View>
<ListMaybePlaceholder
isLoading={isUriLoading || isLikedByLoading}
isError={isError}
emptyType="results"
emptyMessage={_(
msg`Nobody has liked this yet. Maybe you should be the first!`,
)}
errorMessage={cleanError(resolveError || error)}
onRetry={isError ? refetch : undefined}
/>
)
}
return likes.length ? (
return (
<List
data={likes}
keyExtractor={item => item.actor.did}
renderItem={renderItem}
keyExtractor={keyExtractor}
refreshing={isPTRing}
onRefresh={onRefresh}
onEndReached={onEndReached}
onEndReachedThreshold={3}
renderItem={renderItem}
initialNumToRender={initialNumToRender}
ListFooterComponent={() => (
ListFooterComponent={
<ListFooter
isFetching={isFetching && !isRefetching}
isError={isError}
error={error ? error.toString() : undefined}
isFetchingNextPage={isFetchingNextPage}
error={cleanError(error)}
onRetry={fetchNextPage}
/>
)}
}
onEndReachedThreshold={3}
initialNumToRender={initialNumToRender}
windowSize={11}
/>
) : (
<View style={[a.p_lg]}>
<View style={[a.p_lg, a.rounded_sm, t.atoms.bg_contrast_25]}>
<Text style={[a.text_md, a.leading_snug]}>
<Trans>
Nobody has liked this yet. Maybe you should be the first!
</Trans>
</Text>
</View>
</View>
)
}

View file

@ -1,25 +1,23 @@
import React from 'react'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {Trans, msg} from '@lingui/macro'
import {CenteredView} from 'view/com/util/Views'
import {Loader} from '#/components/Loader'
import {cleanError} from 'lib/strings/errors'
import {CenteredView} from 'view/com/util/Views'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button} from '#/components/Button'
import {Text} from '#/components/Typography'
import {Error} from '#/components/Error'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
export function ListFooter({
isFetching,
isError,
isFetchingNextPage,
error,
onRetry,
height,
}: {
isFetching?: boolean
isError?: boolean
isFetchingNextPage?: boolean
error?: string
onRetry?: () => Promise<unknown>
height?: number
@ -36,32 +34,26 @@ export function ListFooter({
t.atoms.border_contrast_low,
{height: height ?? 180, paddingTop: 30},
]}>
{isFetching ? (
{isFetchingNextPage ? (
<Loader size="xl" />
) : (
<ListFooterMaybeError
isError={isError}
error={error}
onRetry={onRetry}
/>
<ListFooterMaybeError error={error} onRetry={onRetry} />
)}
</View>
)
}
function ListFooterMaybeError({
isError,
error,
onRetry,
}: {
isError?: boolean
error?: string
onRetry?: () => Promise<unknown>
}) {
const t = useTheme()
const {_} = useLingui()
if (!isError) return null
if (!error) return null
return (
<View style={[a.w_full, a.px_lg]}>
@ -128,7 +120,7 @@ export function ListHeaderDesktop({
export function ListMaybePlaceholder({
isLoading,
isEmpty,
noEmpty,
isError,
emptyTitle,
emptyMessage,
@ -138,7 +130,7 @@ export function ListMaybePlaceholder({
onRetry,
}: {
isLoading: boolean
isEmpty?: boolean
noEmpty?: boolean
isError?: boolean
emptyTitle?: string
emptyMessage?: string
@ -151,16 +143,6 @@ export function ListMaybePlaceholder({
const {_} = useLingui()
const {gtMobile, gtTablet} = useBreakpoints()
if (!isLoading && isError) {
return (
<Error
title={errorTitle ?? _(msg`Oops!`)}
message={errorMessage ?? _(`Something went wrong!`)}
onRetry={onRetry}
/>
)
}
if (isLoading) {
return (
<CenteredView
@ -180,7 +162,17 @@ export function ListMaybePlaceholder({
)
}
if (isEmpty) {
if (isError) {
return (
<Error
title={errorTitle ?? _(msg`Oops!`)}
message={errorMessage ?? _(`Something went wrong!`)}
onRetry={onRetry}
/>
)
}
if (!noEmpty) {
return (
<Error
title={
@ -197,4 +189,6 @@ export function ListMaybePlaceholder({
/>
)
}
return null
}