diff --git a/src/components/dialogs/StarterPackDialog.tsx b/src/components/dialogs/StarterPackDialog.tsx index ac3861e2b6..0b7759673b 100644 --- a/src/components/dialogs/StarterPackDialog.tsx +++ b/src/components/dialogs/StarterPackDialog.tsx @@ -1,4 +1,4 @@ -import {useCallback, useState} from 'react' +import {useCallback} from 'react' import {View} from 'react-native' import { type AppBskyGraphGetStarterPacksWithMembership, @@ -7,14 +7,12 @@ import { import {msg, Plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' -import {useQueryClient} from '@tanstack/react-query' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' import {type NavigationProp} from '#/lib/routes/types' -import { - invalidateActorStarterPacksWithMembershipQuery, - useActorStarterPacksWithMembershipsQuery, -} from '#/state/queries/actor-starter-packs' +import {isNetworkError} from '#/lib/strings/errors' +import {logger} from '#/logger' +import {useActorStarterPacksWithMembershipsQuery} from '#/state/queries/actor-starter-packs' import { useListMembershipAddMutation, useListMembershipRemoveMutation, @@ -253,50 +251,37 @@ function StarterPackItem({ const t = useTheme() const ax = useAnalytics() const {_} = useLingui() - const queryClient = useQueryClient() const starterPack = starterPackWithMembership.starterPack const isInPack = !!starterPackWithMembership.listItem - const [isPendingRefresh, setIsPendingRefresh] = useState(false) + const {mutate: addMembership, isPending: isPendingAdd} = + useListMembershipAddMutation({ + onSuccess: () => { + Toast.show(_(msg`Added to starter pack`)) + }, + onError: err => { + if (!isNetworkError(err)) { + logger.error('Failed to remove from starter pack', {safeMessage: err}) + } + Toast.show(_(msg`Failed to add to starter pack`), {type: 'error'}) + }, + }) - const {mutate: addMembership} = useListMembershipAddMutation({ - onSuccess: () => { - Toast.show(_(msg`Added to starter pack`)) - // Use a timeout to wait for the appview to update, matching the pattern - // in list-memberships.ts - setTimeout(() => { - invalidateActorStarterPacksWithMembershipQuery({ - queryClient, - did: targetDid, - }) - setIsPendingRefresh(false) - }, 1e3) - }, - onError: () => { - Toast.show(_(msg`Failed to add to starter pack`), 'xmark') - setIsPendingRefresh(false) - }, - }) + const {mutate: removeMembership, isPending: isPendingRemove} = + useListMembershipRemoveMutation({ + onSuccess: () => { + Toast.show(_(msg`Removed from starter pack`)) + }, + onError: err => { + if (!isNetworkError(err)) { + logger.error('Failed to remove from starter pack', {safeMessage: err}) + } + Toast.show(_(msg`Failed to remove from starter pack`), {type: 'error'}) + }, + }) - const {mutate: removeMembership} = useListMembershipRemoveMutation({ - onSuccess: () => { - Toast.show(_(msg`Removed from starter pack`)) - // Use a timeout to wait for the appview to update, matching the pattern - // in list-memberships.ts - setTimeout(() => { - invalidateActorStarterPacksWithMembershipQuery({ - queryClient, - did: targetDid, - }) - setIsPendingRefresh(false) - }, 1e3) - }, - onError: () => { - Toast.show(_(msg`Failed to remove from starter pack`), 'xmark') - setIsPendingRefresh(false) - }, - }) + const isPending = isPendingAdd || isPendingRemove const handleToggleMembership = () => { if (!starterPack.list?.uri || isPending) return @@ -313,7 +298,6 @@ function StarterPackItem({ } else { if (!starterPackWithMembership.listItem?.uri) { console.error('Cannot remove: missing membership URI') - setIsPendingRefresh(false) return } removeMembership({ @@ -379,8 +363,9 @@ function StarterPackItem({ label={isInPack ? _(msg`Remove`) : _(msg`Add`)} color={isInPack ? 'secondary' : 'primary_subtle'} size="tiny" - disabled={isPendingRefresh} + disabled={isPending} onPress={handleToggleMembership}> + {isPending && } {isInPack ? Remove : Add} diff --git a/src/state/queries/actor-starter-packs.ts b/src/state/queries/actor-starter-packs.ts index d40e054535..97cc3b5e05 100644 --- a/src/state/queries/actor-starter-packs.ts +++ b/src/state/queries/actor-starter-packs.ts @@ -1,13 +1,4 @@ -import { - type AppBskyGraphGetActorStarterPacks, - type AppBskyGraphGetStarterPacksWithMembership, -} from '@atproto/api' -import { - type InfiniteData, - type QueryClient, - type QueryKey, - useInfiniteQuery, -} from '@tanstack/react-query' +import {type QueryClient, useInfiniteQuery} from '@tanstack/react-query' import {useAgent} from '#/state/session' @@ -28,13 +19,7 @@ export function useActorStarterPacksQuery({ }) { const agent = useAgent() - return useInfiniteQuery< - AppBskyGraphGetActorStarterPacks.OutputSchema, - Error, - InfiniteData, - QueryKey, - string | undefined - >({ + return useInfiniteQuery({ queryKey: RQKEY(did), queryFn: async ({pageParam}: {pageParam?: string}) => { const res = await agent.app.bsky.graph.getActorStarterPacks({ @@ -59,13 +44,7 @@ export function useActorStarterPacksWithMembershipsQuery({ }) { const agent = useAgent() - return useInfiniteQuery< - AppBskyGraphGetStarterPacksWithMembership.OutputSchema, - Error, - InfiniteData, - QueryKey, - string | undefined - >({ + return useInfiniteQuery({ queryKey: RQKEY_WITH_MEMBERSHIP(did), queryFn: async ({pageParam}: {pageParam?: string}) => { const res = await agent.app.bsky.graph.getStarterPacksWithMembership({ diff --git a/src/state/queries/list-memberships.ts b/src/state/queries/list-memberships.ts index 410a613ada..a7657bb572 100644 --- a/src/state/queries/list-memberships.ts +++ b/src/state/queries/list-memberships.ts @@ -14,12 +14,22 @@ * -prf */ -import {AtUri} from '@atproto/api' -import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' +import { + type AppBskyActorDefs, + type AppBskyGraphGetStarterPacksWithMembership, + AtUri, +} from '@atproto/api' +import { + type InfiniteData, + useMutation, + useQuery, + useQueryClient, +} from '@tanstack/react-query' import {STALE} from '#/state/queries' import {RQKEY as LIST_MEMBERS_RQKEY} from '#/state/queries/list-members' import {useAgent, useSession} from '#/state/session' +import {RQKEY_WITH_MEMBERSHIP as STARTER_PACKS_WITH_MEMBERSHIPS_RKEY} from './actor-starter-packs' // sanity limit is SANITY_PAGE_LIMIT*PAGE_SIZE total records const SANITY_PAGE_LIMIT = 1000 @@ -123,7 +133,7 @@ export function useListMembershipAddMutation({ // -prf return res }, - onSuccess: (data, variables) => { + onSuccess: async (data, variables) => { // manually update the cache; a refetch is too expensive let memberships = queryClient.getQueryData(RQKEY()) if (memberships) { @@ -151,6 +161,42 @@ export function useListMembershipAddMutation({ queryKey: LIST_MEMBERS_RQKEY(variables.listUri), }) }, 1e3) + + // update WITH_MEMBERSHIPS query + const subject = await agent + .getProfile({actor: variables.actorDid}) + .then(res => res.data) + + if (subject) { + queryClient.setQueryData< + InfiniteData + >(STARTER_PACKS_WITH_MEMBERSHIPS_RKEY(variables.actorDid), old => { + if (!old) return old + + return { + ...old, + pages: old.pages.map(page => ({ + ...page, + starterPacksWithMembership: page.starterPacksWithMembership.map( + list => { + if (list.starterPack.list?.uri === variables.listUri) { + return { + ...list, + listItem: { + uri: data.uri, + subject: subject as AppBskyActorDefs.ProfileView, + }, + } + } + + return list + }, + ), + })), + } + }) + } + onSuccess?.(data) }, onError, @@ -206,6 +252,32 @@ export function useListMembershipRemoveMutation({ queryKey: LIST_MEMBERS_RQKEY(variables.listUri), }) }, 1e3) + + queryClient.setQueryData< + InfiniteData + >(STARTER_PACKS_WITH_MEMBERSHIPS_RKEY(variables.actorDid), old => { + if (!old) return old + + return { + ...old, + pages: old.pages.map(page => ({ + ...page, + starterPacksWithMembership: page.starterPacksWithMembership.map( + list => { + if (list.starterPack.list?.uri === variables.listUri) { + return { + ...list, + listItem: undefined, + } + } + + return list + }, + ), + })), + } + }) + onSuccess?.(data) }, onError,