about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/Error.tsx4
-rw-r--r--src/components/LikedByList.tsx96
-rw-r--r--src/components/Lists.tsx56
-rw-r--r--src/lib/icons.tsx2
-rw-r--r--src/screens/Hashtag.tsx60
-rw-r--r--src/screens/Profile/ProfileLabelerLikedBy.tsx21
-rw-r--r--src/view/com/post-thread/PostThread.tsx71
-rw-r--r--src/view/com/profile/ProfileFollowers.tsx79
-rw-r--r--src/view/com/profile/ProfileFollows.tsx80
9 files changed, 226 insertions, 243 deletions
diff --git a/src/components/Error.tsx b/src/components/Error.tsx
index 7df166c3f..91b33f48e 100644
--- a/src/components/Error.tsx
+++ b/src/components/Error.tsx
@@ -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'
diff --git a/src/components/LikedByList.tsx b/src/components/LikedByList.tsx
index bd1213639..239a7044f 100644
--- a/src/components/LikedByList.tsx
+++ b/src/components/LikedByList.tsx
@@ -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])
-
-  const renderItem = React.useCallback(({item}: {item: GetLikes.Like}) => {
-    return (
-      <ProfileCardWithFollowBtn key={item.actor.did} profile={item.actor} />
-    )
-  }, [])
+  }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
 
-  if (isFetchingResolvedUri || !isFetched) {
+  if (likes.length < 1) {
     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>
   )
 }
diff --git a/src/components/Lists.tsx b/src/components/Lists.tsx
index d3e072028..605626fef 100644
--- a/src/components/Lists.tsx
+++ b/src/components/Lists.tsx
@@ -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
 }
diff --git a/src/lib/icons.tsx b/src/lib/icons.tsx
index 8783682d0..93b45ea3a 100644
--- a/src/lib/icons.tsx
+++ b/src/lib/icons.tsx
@@ -1,6 +1,6 @@
 import React from 'react'
 import {StyleProp, TextStyle, ViewStyle} from 'react-native'
-import Svg, {Path, Rect, Line, Ellipse} from 'react-native-svg'
+import Svg, {Ellipse, Line, Path, Rect} from 'react-native-svg'
 
 export function GridIcon({
   style,
diff --git a/src/screens/Hashtag.tsx b/src/screens/Hashtag.tsx
index 46452f087..5388593f1 100644
--- a/src/screens/Hashtag.tsx
+++ b/src/screens/Hashtag.tsx
@@ -1,28 +1,30 @@
 import React from 'react'
 import {ListRenderItemInfo, Pressable} from 'react-native'
+import {PostView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
 import {useFocusEffect} from '@react-navigation/native'
-import {useSetMinimalShellMode} from 'state/shell'
-import {ViewHeader} from 'view/com/util/ViewHeader'
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
+
+import {HITSLOP_10} from 'lib/constants'
+import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
 import {CommonNavigatorParams} from 'lib/routes/types'
+import {shareUrl} from 'lib/sharing'
+import {cleanError} from 'lib/strings/errors'
+import {sanitizeHandle} from 'lib/strings/handles'
+import {enforceLen} from 'lib/strings/helpers'
+import {isNative} from 'platform/detection'
 import {useSearchPostsQuery} from 'state/queries/search-posts'
+import {useSetMinimalShellMode} from 'state/shell'
 import {Post} from 'view/com/post/Post'
-import {PostView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
-import {enforceLen} from 'lib/strings/helpers'
+import {List} from 'view/com/util/List'
+import {ViewHeader} from 'view/com/util/ViewHeader'
+import {ArrowOutOfBox_Stroke2_Corner0_Rounded} from '#/components/icons/ArrowOutOfBox'
 import {
   ListFooter,
   ListHeaderDesktop,
   ListMaybePlaceholder,
 } from '#/components/Lists'
-import {List} from 'view/com/util/List'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {sanitizeHandle} from 'lib/strings/handles'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded} from '#/components/icons/ArrowOutOfBox'
-import {shareUrl} from 'lib/sharing'
-import {HITSLOP_10} from 'lib/constants'
-import {isNative} from 'platform/detection'
-import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
 
 const renderItem = ({item}: ListRenderItemInfo<PostView>) => {
   return <Post post={item} />
@@ -61,9 +63,8 @@ export default function HashtagScreen({
 
   const {
     data,
-    isFetching,
+    isFetchingNextPage,
     isLoading,
-    isRefetching,
     isError,
     error,
     refetch,
@@ -97,9 +98,9 @@ export default function HashtagScreen({
   }, [refetch])
 
   const onEndReached = React.useCallback(() => {
-    if (isFetching || !hasNextPage || error) return
+    if (isFetchingNextPage || !hasNextPage || error) return
     fetchNextPage()
-  }, [isFetching, hasNextPage, error, fetchNextPage])
+  }, [isFetchingNextPage, hasNextPage, error, fetchNextPage])
 
   return (
     <>
@@ -123,16 +124,16 @@ export default function HashtagScreen({
             : undefined
         }
       />
-      <ListMaybePlaceholder
-        isLoading={isLoading || isRefetching}
-        isError={isError}
-        isEmpty={posts.length < 1}
-        onRetry={refetch}
-        emptyTitle="results"
-        emptyMessage={_(msg`We couldn't find any results for that hashtag.`)}
-      />
-      {!isLoading && posts.length > 0 && (
-        <List<PostView>
+      {posts.length < 1 ? (
+        <ListMaybePlaceholder
+          isLoading={isLoading}
+          isError={isError}
+          onRetry={refetch}
+          emptyType="results"
+          emptyMessage={_(msg`We couldn't find any results for that hashtag.`)}
+        />
+      ) : (
+        <List
           data={posts}
           renderItem={renderItem}
           keyExtractor={keyExtractor}
@@ -150,9 +151,8 @@ export default function HashtagScreen({
           }
           ListFooterComponent={
             <ListFooter
-              isFetching={isFetching && !isRefetching}
-              isError={isError}
-              error={error?.name}
+              isFetchingNextPage={isFetchingNextPage}
+              error={cleanError(error)}
               onRetry={fetchNextPage}
             />
           }
diff --git a/src/screens/Profile/ProfileLabelerLikedBy.tsx b/src/screens/Profile/ProfileLabelerLikedBy.tsx
index 1d2167520..8650ac2e6 100644
--- a/src/screens/Profile/ProfileLabelerLikedBy.tsx
+++ b/src/screens/Profile/ProfileLabelerLikedBy.tsx
@@ -4,13 +4,11 @@ import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useFocusEffect} from '@react-navigation/native'
 
-import {NativeStackScreenProps, CommonNavigatorParams} from '#/lib/routes/types'
+import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
+import {makeRecordUri} from '#/lib/strings/url-helpers'
+import {useSetMinimalShellMode} from '#/state/shell'
 import {ViewHeader} from '#/view/com/util/ViewHeader'
 import {LikedByList} from '#/components/LikedByList'
-import {useSetMinimalShellMode} from '#/state/shell'
-import {makeRecordUri} from '#/lib/strings/url-helpers'
-
-import {atoms as a, useBreakpoints} from '#/alf'
 
 export function ProfileLabelerLikedByScreen({
   route,
@@ -19,7 +17,6 @@ export function ProfileLabelerLikedByScreen({
   const {name: handleOrDid} = route.params
   const uri = makeRecordUri(handleOrDid, 'app.bsky.labeler.service', 'self')
   const {_} = useLingui()
-  const {gtMobile} = useBreakpoints()
 
   useFocusEffect(
     React.useCallback(() => {
@@ -28,17 +25,7 @@ export function ProfileLabelerLikedByScreen({
   )
 
   return (
-    <View
-      style={[
-        a.mx_auto,
-        a.w_full,
-        a.h_full_vh,
-        gtMobile && [
-          {
-            maxWidth: 600,
-          },
-        ],
-      ]}>
+    <View style={{flex: 1}}>
       <ViewHeader title={_(msg`Liked By`)} />
       <LikedByList uri={uri} />
     </View>
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index c1159379d..f4bf3b1ac 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -368,47 +368,52 @@ export function PostThread({
     ],
   )
 
-  return (
-    <>
+  if (error || !thread) {
+    return (
       <ListMaybePlaceholder
         isLoading={(!preferences || !thread) && !error}
         isError={!!error}
+        noEmpty
         onRetry={refetch}
         errorTitle={error?.title}
         errorMessage={error?.message}
       />
-      {!error && thread && (
-        <List
-          ref={ref}
-          data={posts}
-          renderItem={renderItem}
-          keyExtractor={keyExtractor}
-          onContentSizeChange={isNative ? undefined : onContentSizeChangeWeb}
-          onStartReached={onStartReached}
-          onEndReached={onEndReached}
-          onEndReachedThreshold={2}
-          onMomentumScrollEnd={onMomentumScrollEnd}
-          onScrollToTop={onScrollToTop}
-          maintainVisibleContentPosition={
-            isNative ? MAINTAIN_VISIBLE_CONTENT_POSITION : undefined
-          }
-          // @ts-ignore our .web version only -prf
-          desktopFixedHeight
-          removeClippedSubviews={isAndroid ? false : undefined}
-          ListFooterComponent={
-            <ListFooter
-              isFetching={isFetching}
-              onRetry={refetch}
-              // 300 is based on the minimum height of a post. This is enough extra height for the `maintainVisPos` to
-              // work without causing weird jumps on web or glitches on native
-              height={windowHeight - 200}
-            />
-          }
-          initialNumToRender={initialNumToRender}
-          windowSize={11}
+    )
+  }
+
+  return (
+    <List
+      ref={ref}
+      data={posts}
+      renderItem={renderItem}
+      keyExtractor={keyExtractor}
+      onContentSizeChange={isNative ? undefined : onContentSizeChangeWeb}
+      onStartReached={onStartReached}
+      onEndReached={onEndReached}
+      onEndReachedThreshold={2}
+      onMomentumScrollEnd={onMomentumScrollEnd}
+      onScrollToTop={onScrollToTop}
+      maintainVisibleContentPosition={
+        isNative ? MAINTAIN_VISIBLE_CONTENT_POSITION : undefined
+      }
+      // @ts-ignore our .web version only -prf
+      desktopFixedHeight
+      removeClippedSubviews={isAndroid ? false : undefined}
+      ListFooterComponent={
+        <ListFooter
+          // Using `isFetching` over `isFetchingNextPage` is done on purpose here so we get the loader on
+          // initial render
+          isFetchingNextPage={isFetching}
+          error={cleanError(threadError)}
+          onRetry={refetch}
+          // 300 is based on the minimum height of a post. This is enough extra height for the `maintainVisPos` to
+          // work without causing weird jumps on web or glitches on native
+          height={windowHeight - 200}
         />
-      )}
-    </>
+      }
+      initialNumToRender={initialNumToRender}
+      windowSize={11}
+    />
   )
 }
 
diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx
index b11a33f27..94ca33e6e 100644
--- a/src/view/com/profile/ProfileFollowers.tsx
+++ b/src/view/com/profile/ProfileFollowers.tsx
@@ -1,21 +1,21 @@
 import React from 'react'
 import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
-import {List} from '../util/List'
-import {ProfileCardWithFollowBtn} from './ProfileCard'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {cleanError} from '#/lib/strings/errors'
+import {logger} from '#/logger'
 import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
 import {useResolveDidQuery} from '#/state/queries/resolve-uri'
-import {logger} from '#/logger'
-import {cleanError} from '#/lib/strings/errors'
 import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
+import {useSession} from 'state/session'
 import {
   ListFooter,
   ListHeaderDesktop,
   ListMaybePlaceholder,
 } from '#/components/Lists'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {useSession} from 'state/session'
-import {View} from 'react-native'
+import {List} from '../util/List'
+import {ProfileCardWithFollowBtn} from './ProfileCard'
 
 function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) {
   return <ProfileCardWithFollowBtn key={item.did} profile={item} />
@@ -39,7 +39,6 @@ export function ProfileFollowers({name}: {name: string}) {
   const {
     data,
     isLoading: isFollowersLoading,
-    isFetching,
     isFetchingNextPage,
     hasNextPage,
     fetchNextPage,
@@ -47,14 +46,8 @@ export function ProfileFollowers({name}: {name: string}) {
     refetch,
   } = useProfileFollowersQuery(resolvedDid)
 
-  const isError = React.useMemo(
-    () => !!resolveError || !!error,
-    [resolveError, error],
-  )
-
-  const isMe = React.useMemo(() => {
-    return resolvedDid === currentAccount?.did
-  }, [resolvedDid, currentAccount?.did])
+  const isError = !!resolveError || !!error
+  const isMe = resolvedDid === currentAccount?.did
 
   const followers = React.useMemo(() => {
     if (data?.pages) {
@@ -73,20 +66,19 @@ export function ProfileFollowers({name}: {name: string}) {
     setIsPTRing(false)
   }, [refetch, setIsPTRing])
 
-  const onEndReached = async () => {
-    if (isFetching || !hasNextPage || !!error) return
+  const onEndReached = React.useCallback(async () => {
+    if (isFetchingNextPage || !hasNextPage || !!error) return
     try {
       await fetchNextPage()
     } catch (err) {
       logger.error('Failed to load more followers', {message: err})
     }
-  }
+  }, [isFetchingNextPage, hasNextPage, error, fetchNextPage])
 
-  return (
-    <View style={{flex: 1}}>
+  if (followers.length < 1) {
+    return (
       <ListMaybePlaceholder
         isLoading={isDidLoading || isFollowersLoading}
-        isEmpty={followers.length < 1}
         isError={isError}
         emptyType="results"
         emptyMessage={
@@ -97,23 +89,30 @@ export function ProfileFollowers({name}: {name: string}) {
         errorMessage={cleanError(resolveError || error)}
         onRetry={isError ? refetch : undefined}
       />
-      {followers.length > 0 && (
-        <List
-          data={followers}
-          renderItem={renderItem}
-          keyExtractor={keyExtractor}
-          refreshing={isPTRing}
-          onRefresh={onRefresh}
-          onEndReached={onEndReached}
-          onEndReachedThreshold={4}
-          ListHeaderComponent={<ListHeaderDesktop title={_(msg`Followers`)} />}
-          ListFooterComponent={<ListFooter isFetching={isFetchingNextPage} />}
-          // @ts-ignore our .web version only -prf
-          desktopFixedHeight
-          initialNumToRender={initialNumToRender}
-          windowSize={11}
+    )
+  }
+
+  return (
+    <List
+      data={followers}
+      renderItem={renderItem}
+      keyExtractor={keyExtractor}
+      refreshing={isPTRing}
+      onRefresh={onRefresh}
+      onEndReached={onEndReached}
+      onEndReachedThreshold={4}
+      ListHeaderComponent={<ListHeaderDesktop title={_(msg`Followers`)} />}
+      ListFooterComponent={
+        <ListFooter
+          isFetchingNextPage={isFetchingNextPage}
+          error={cleanError(error)}
+          onRetry={fetchNextPage}
         />
-      )}
-    </View>
+      }
+      // @ts-ignore our .web version only -prf
+      desktopFixedHeight
+      initialNumToRender={initialNumToRender}
+      windowSize={11}
+    />
   )
 }
diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx
index d99e2b840..9b447c955 100644
--- a/src/view/com/profile/ProfileFollows.tsx
+++ b/src/view/com/profile/ProfileFollows.tsx
@@ -1,20 +1,21 @@
 import React from 'react'
 import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
-import {List} from '../util/List'
-import {ProfileCardWithFollowBtn} from './ProfileCard'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {cleanError} from '#/lib/strings/errors'
+import {logger} from '#/logger'
 import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
 import {useResolveDidQuery} from '#/state/queries/resolve-uri'
-import {logger} from '#/logger'
-import {cleanError} from '#/lib/strings/errors'
+import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
+import {useSession} from 'state/session'
 import {
   ListFooter,
   ListHeaderDesktop,
   ListMaybePlaceholder,
 } from '#/components/Lists'
-import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
-import {useSession} from 'state/session'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
+import {List} from '../util/List'
+import {ProfileCardWithFollowBtn} from './ProfileCard'
 
 function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) {
   return <ProfileCardWithFollowBtn key={item.did} profile={item} />
@@ -38,7 +39,6 @@ export function ProfileFollows({name}: {name: string}) {
   const {
     data,
     isLoading: isFollowsLoading,
-    isFetching,
     isFetchingNextPage,
     hasNextPage,
     fetchNextPage,
@@ -46,14 +46,8 @@ export function ProfileFollows({name}: {name: string}) {
     refetch,
   } = useProfileFollowsQuery(resolvedDid)
 
-  const isError = React.useMemo(
-    () => !!resolveError || !!error,
-    [resolveError, error],
-  )
-
-  const isMe = React.useMemo(() => {
-    return resolvedDid === currentAccount?.did
-  }, [resolvedDid, currentAccount?.did])
+  const isError = !!resolveError || !!error
+  const isMe = resolvedDid === currentAccount?.did
 
   const follows = React.useMemo(() => {
     if (data?.pages) {
@@ -72,20 +66,19 @@ export function ProfileFollows({name}: {name: string}) {
     setIsPTRing(false)
   }, [refetch, setIsPTRing])
 
-  const onEndReached = async () => {
-    if (isFetching || !hasNextPage || !!error) return
+  const onEndReached = React.useCallback(async () => {
+    if (isFetchingNextPage || !hasNextPage || !!error) return
     try {
       await fetchNextPage()
     } catch (err) {
       logger.error('Failed to load more follows', {error: err})
     }
-  }
+  }, [error, fetchNextPage, hasNextPage, isFetchingNextPage])
 
-  return (
-    <>
+  if (follows.length < 1) {
+    return (
       <ListMaybePlaceholder
         isLoading={isDidLoading || isFollowsLoading}
-        isEmpty={follows.length < 1}
         isError={isError}
         emptyType="results"
         emptyMessage={
@@ -96,23 +89,30 @@ export function ProfileFollows({name}: {name: string}) {
         errorMessage={cleanError(resolveError || error)}
         onRetry={isError ? refetch : undefined}
       />
-      {follows.length > 0 && (
-        <List
-          data={follows}
-          renderItem={renderItem}
-          keyExtractor={keyExtractor}
-          refreshing={isPTRing}
-          onRefresh={onRefresh}
-          onEndReached={onEndReached}
-          onEndReachedThreshold={4}
-          ListHeaderComponent={<ListHeaderDesktop title={_(msg`Following`)} />}
-          ListFooterComponent={<ListFooter isFetching={isFetchingNextPage} />}
-          // @ts-ignore our .web version only -prf
-          desktopFixedHeight
-          initialNumToRender={initialNumToRender}
-          windowSize={11}
+    )
+  }
+
+  return (
+    <List
+      data={follows}
+      renderItem={renderItem}
+      keyExtractor={keyExtractor}
+      refreshing={isPTRing}
+      onRefresh={onRefresh}
+      onEndReached={onEndReached}
+      onEndReachedThreshold={4}
+      ListHeaderComponent={<ListHeaderDesktop title={_(msg`Following`)} />}
+      ListFooterComponent={
+        <ListFooter
+          isFetchingNextPage={isFetchingNextPage}
+          error={cleanError(error)}
+          onRetry={fetchNextPage}
         />
-      )}
-    </>
+      }
+      // @ts-ignore our .web version only -prf
+      desktopFixedHeight
+      initialNumToRender={initialNumToRender}
+      windowSize={11}
+    />
   )
 }