diff options
author | Samuel Newman <mozzius@protonmail.com> | 2025-02-24 11:50:52 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-24 11:50:52 -0800 |
commit | f9392d4a9688c1ea1bff962c857ff4ae543e46fe (patch) | |
tree | d84b3adc155c03e7519c0277a0e6a32366a76ea6 /src | |
parent | bdcddee7a568519073939e434f9e3973497b304c (diff) | |
download | voidsky-f9392d4a9688c1ea1bff962c857ff4ae543e46fe.tar.zst |
Fix optimistic rendering of profile page (#7830)
* don't hold up rendering on starter packs * add tab based on profile.associated * move query into component, fix pending states
Diffstat (limited to 'src')
-rw-r--r-- | src/components/StarterPack/ProfileStarterPacks.tsx | 57 | ||||
-rw-r--r-- | src/state/queries/actor-starter-packs.ts | 10 | ||||
-rw-r--r-- | src/view/com/feeds/ProfileFeedgens.tsx | 2 | ||||
-rw-r--r-- | src/view/com/lists/ProfileLists.tsx | 2 | ||||
-rw-r--r-- | src/view/screens/Profile.tsx | 21 |
5 files changed, 42 insertions, 50 deletions
diff --git a/src/components/StarterPack/ProfileStarterPacks.tsx b/src/components/StarterPack/ProfileStarterPacks.tsx index d8925684b..da5f0dc6a 100644 --- a/src/components/StarterPack/ProfileStarterPacks.tsx +++ b/src/components/StarterPack/ProfileStarterPacks.tsx @@ -1,4 +1,9 @@ -import React from 'react' +import React, { + useCallback, + useEffect, + useImperativeHandle, + useState, +} from 'react' import { findNodeHandle, ListRenderItemInfo, @@ -6,11 +11,10 @@ import { View, ViewStyle, } from 'react-native' -import {AppBskyGraphDefs, AppBskyGraphGetActorStarterPacks} from '@atproto/api' +import {AppBskyGraphDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' -import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query' import {useGenerateStarterPackMutation} from '#/lib/generate-starterpack' import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' @@ -19,28 +23,27 @@ import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {NavigationProp} from '#/lib/routes/types' import {parseStarterPackUri} from '#/lib/strings/starter-pack' import {logger} from '#/logger' +import {useActorStarterPacksQuery} from '#/state/queries/actor-starter-packs' import {List, ListRef} from '#/view/com/util/List' -import {Text} from '#/view/com/util/text/Text' +import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {atoms as a, ios, useTheme} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' +import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog' +import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {LinearGradientBackground} from '#/components/LinearGradientBackground' import {Loader} from '#/components/Loader' import * as Prompt from '#/components/Prompt' import {Default as StarterPackCard} from '#/components/StarterPack/StarterPackCard' -import {VerifyEmailDialog} from '../dialogs/VerifyEmailDialog' -import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '../icons/Plus' +import {Text} from '#/components/Typography' interface SectionRef { scrollToTop: () => void } interface ProfileFeedgensProps { - starterPacksQuery: UseInfiniteQueryResult< - InfiniteData<AppBskyGraphGetActorStarterPacks.OutputSchema, unknown>, - Error - > scrollElRef: ListRef + did: string headerOffset: number enabled?: boolean style?: StyleProp<ViewStyle> @@ -58,8 +61,8 @@ export const ProfileStarterPacks = React.forwardRef< ProfileFeedgensProps >(function ProfileFeedgensImpl( { - starterPacksQuery: query, scrollElRef, + did, headerOffset, enabled, style, @@ -71,17 +74,18 @@ export const ProfileStarterPacks = React.forwardRef< ) { const t = useTheme() const bottomBarOffset = useBottomBarOffset(100) - const [isPTRing, setIsPTRing] = React.useState(false) - const {data, refetch, isFetching, hasNextPage, fetchNextPage} = query + const [isPTRing, setIsPTRing] = useState(false) + const {data, refetch, isFetching, hasNextPage, fetchNextPage} = + useActorStarterPacksQuery({did, enabled}) const {isTabletOrDesktop} = useWebMediaQueries() const items = data?.pages.flatMap(page => page.starterPacks) - React.useImperativeHandle(ref, () => ({ + useImperativeHandle(ref, () => ({ scrollToTop: () => {}, })) - const onRefresh = React.useCallback(async () => { + const onRefresh = useCallback(async () => { setIsPTRing(true) try { await refetch() @@ -91,7 +95,7 @@ export const ProfileStarterPacks = React.forwardRef< setIsPTRing(false) }, [refetch, setIsPTRing]) - const onEndReached = React.useCallback(async () => { + const onEndReached = useCallback(async () => { if (isFetching || !hasNextPage) return try { @@ -101,7 +105,7 @@ export const ProfileStarterPacks = React.forwardRef< } }, [isFetching, hasNextPage, fetchNextPage]) - React.useEffect(() => { + useEffect(() => { if (enabled && scrollElRef.current) { const nativeTag = findNodeHandle(scrollElRef.current) setScrollViewTag(nativeTag) @@ -140,9 +144,11 @@ export const ProfileStarterPacks = React.forwardRef< desktopFixedHeight onEndReached={onEndReached} onRefresh={onRefresh} - ListEmptyComponent={Empty} + ListEmptyComponent={ + data ? (isMe ? Empty : undefined) : FeedLoadingPlaceholder + } ListFooterComponent={ - items?.length !== 0 && isMe ? CreateAnother : undefined + !!data && items?.length !== 0 && isMe ? CreateAnother : undefined } /> </View> @@ -181,7 +187,6 @@ function CreateAnother() { function Empty() { const {_} = useLingui() - const t = useTheme() const navigation = useNavigation<NavigationProp>() const confirmDialogControl = useDialogControl() const followersDialogControl = useDialogControl() @@ -190,7 +195,7 @@ function Empty() { const {needsEmailVerification} = useEmail() const verifyEmailControl = useDialogControl() - const [isGenerating, setIsGenerating] = React.useState(false) + const [isGenerating, setIsGenerating] = useState(false) const {mutate: generateStarterPack} = useGenerateStarterPackMutation({ onSuccess: ({uri}) => { @@ -227,16 +232,10 @@ function Empty() { a.justify_between, a.gap_lg, a.shadow_lg, - {marginTop: 1}, + {marginTop: a.border.borderWidth}, ]}> <View style={[a.gap_xs]}> - <Text - style={[ - a.font_bold, - a.text_lg, - t.atoms.text_contrast_medium, - {color: 'white'}, - ]}> + <Text style={[a.font_bold, a.text_lg, {color: 'white'}]}> <Trans>You haven't created a starter pack yet!</Trans> </Text> <Text style={[a.text_md, {color: 'white'}]}> diff --git a/src/state/queries/actor-starter-packs.ts b/src/state/queries/actor-starter-packs.ts index 487bcdfd9..670544dfe 100644 --- a/src/state/queries/actor-starter-packs.ts +++ b/src/state/queries/actor-starter-packs.ts @@ -11,7 +11,13 @@ import {useAgent} from '#/state/session' export const RQKEY_ROOT = 'actor-starter-packs' export const RQKEY = (did?: string) => [RQKEY_ROOT, did] -export function useActorStarterPacksQuery({did}: {did?: string}) { +export function useActorStarterPacksQuery({ + did, + enabled = true, +}: { + did?: string + enabled?: boolean +}) { const agent = useAgent() return useInfiniteQuery< @@ -30,7 +36,7 @@ export function useActorStarterPacksQuery({did}: {did?: string}) { }) return res.data }, - enabled: Boolean(did), + enabled: Boolean(did) && enabled, initialPageParam: undefined, getNextPageParam: lastPage => lastPage.cursor, }) diff --git a/src/view/com/feeds/ProfileFeedgens.tsx b/src/view/com/feeds/ProfileFeedgens.tsx index f5894f9ee..b55c6b9bd 100644 --- a/src/view/com/feeds/ProfileFeedgens.tsx +++ b/src/view/com/feeds/ProfileFeedgens.tsx @@ -76,7 +76,7 @@ export const ProfileFeedgens = React.forwardRef< if (isError && isEmpty) { items = items.concat([ERROR_ITEM]) } - if (!isFetched && isFetching) { + if (!isFetched || isFetching) { items = items.concat([LOADING]) } else if (isEmpty) { items = items.concat([EMPTY]) diff --git a/src/view/com/lists/ProfileLists.tsx b/src/view/com/lists/ProfileLists.tsx index d91a4fb66..3a0b0b198 100644 --- a/src/view/com/lists/ProfileLists.tsx +++ b/src/view/com/lists/ProfileLists.tsx @@ -72,7 +72,7 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>( if (isError && isEmpty) { items = items.concat([ERROR_ITEM]) } - if (!isFetched && isFetching) { + if (!isFetched || isFetching) { items = items.concat([LOADING]) } else if (isEmpty) { items = items.concat([EMPTY]) diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 4e0ac259f..f781ba2a8 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -3,7 +3,6 @@ import {StyleSheet} from 'react-native' import {SafeAreaView} from 'react-native-safe-area-context' import { AppBskyActorDefs, - AppBskyGraphGetActorStarterPacks, moderateProfile, ModerationOpts, RichText as RichTextAPI, @@ -11,11 +10,7 @@ import { import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' -import { - InfiniteData, - UseInfiniteQueryResult, - useQueryClient, -} from '@tanstack/react-query' +import {useQueryClient} from '@tanstack/react-query' import {useSetTitle} from '#/lib/hooks/useSetTitle' import {ComposeIcon2} from '#/lib/icons' @@ -27,7 +22,6 @@ import {colors, s} from '#/lib/styles' import {useProfileShadow} from '#/state/cache/profile-shadow' import {listenSoftReset} from '#/state/events' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {useActorStarterPacksQuery} from '#/state/queries/actor-starter-packs' import {useLabelerInfoQuery} from '#/state/queries/labeler' import {resetProfilePostsQueries} from '#/state/queries/post-feed' import {useProfileQuery} from '#/state/queries/profile' @@ -86,7 +80,6 @@ function ProfileScreenInner({route}: Props) { } = useProfileQuery({ did: resolvedDid, }) - const starterPacksQuery = useActorStarterPacksQuery({did: resolvedDid}) const onPressTryAgain = React.useCallback(() => { if (resolveError) { @@ -114,7 +107,7 @@ function ProfileScreenInner({route}: Props) { }, [queryClient, profile?.viewer?.blockedBy, resolvedDid]) // Most pushes will happen here, since we will have only placeholder data - if (isLoadingDid || isLoadingProfile || starterPacksQuery.isLoading) { + if (isLoadingDid || isLoadingProfile) { return ( <Layout.Content> <ProfileHeaderLoading /> @@ -138,7 +131,6 @@ function ProfileScreenInner({route}: Props) { return ( <ProfileScreenLoaded profile={profile} - starterPacksQuery={starterPacksQuery} moderationOpts={moderationOpts} isPlaceholderProfile={isPlaceholderProfile} hideBackButton={!!route.params.hideBackButton} @@ -164,16 +156,11 @@ function ProfileScreenLoaded({ isPlaceholderProfile, moderationOpts, hideBackButton, - starterPacksQuery, }: { profile: AppBskyActorDefs.ProfileViewDetailed moderationOpts: ModerationOpts hideBackButton: boolean isPlaceholderProfile: boolean - starterPacksQuery: UseInfiniteQueryResult< - InfiniteData<AppBskyGraphGetActorStarterPacks.OutputSchema, unknown>, - Error - > }) { const profile = useProfileShadow(profileUnshadowed) const {hasSession, currentAccount} = useSession() @@ -223,7 +210,7 @@ function ProfileScreenLoaded({ const showLikesTab = isMe const showFeedsTab = isMe || (profile.associated?.feedgens || 0) > 0 const showStarterPacksTab = - isMe || !!starterPacksQuery.data?.pages?.[0].starterPacks.length + isMe || (profile.associated?.starterPacks || 0) > 0 const showListsTab = hasSession && (isMe || (profile.associated?.lists || 0) > 0) @@ -487,8 +474,8 @@ function ProfileScreenLoaded({ ? ({headerHeight, isFocused, scrollElRef}) => ( <ProfileStarterPacks ref={starterPacksSectionRef} + did={profile.did} isMe={isMe} - starterPacksQuery={starterPacksQuery} scrollElRef={scrollElRef as ListRef} headerOffset={headerHeight} enabled={isFocused} |