add actual optimistic updates
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import {useCallback, useState} from 'react'
|
import {useCallback} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {
|
import {
|
||||||
type AppBskyGraphGetStarterPacksWithMembership,
|
type AppBskyGraphGetStarterPacksWithMembership,
|
||||||
@@ -7,14 +7,12 @@ import {
|
|||||||
import {msg, Plural, Trans} from '@lingui/macro'
|
import {msg, Plural, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
|
||||||
|
|
||||||
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
|
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
|
||||||
import {type NavigationProp} from '#/lib/routes/types'
|
import {type NavigationProp} from '#/lib/routes/types'
|
||||||
import {
|
import {isNetworkError} from '#/lib/strings/errors'
|
||||||
invalidateActorStarterPacksWithMembershipQuery,
|
import {logger} from '#/logger'
|
||||||
useActorStarterPacksWithMembershipsQuery,
|
import {useActorStarterPacksWithMembershipsQuery} from '#/state/queries/actor-starter-packs'
|
||||||
} from '#/state/queries/actor-starter-packs'
|
|
||||||
import {
|
import {
|
||||||
useListMembershipAddMutation,
|
useListMembershipAddMutation,
|
||||||
useListMembershipRemoveMutation,
|
useListMembershipRemoveMutation,
|
||||||
@@ -253,51 +251,38 @@ function StarterPackItem({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const queryClient = useQueryClient()
|
|
||||||
|
|
||||||
const starterPack = starterPackWithMembership.starterPack
|
const starterPack = starterPackWithMembership.starterPack
|
||||||
const isInPack = !!starterPackWithMembership.listItem
|
const isInPack = !!starterPackWithMembership.listItem
|
||||||
|
|
||||||
const [isPendingRefresh, setIsPendingRefresh] = useState(false)
|
const {mutate: addMembership, isPending: isPendingAdd} =
|
||||||
|
useListMembershipAddMutation({
|
||||||
const {mutate: addMembership} = useListMembershipAddMutation({
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
Toast.show(_(msg`Added to starter pack`))
|
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: () => {
|
onError: err => {
|
||||||
Toast.show(_(msg`Failed to add to starter pack`), 'xmark')
|
if (!isNetworkError(err)) {
|
||||||
setIsPendingRefresh(false)
|
logger.error('Failed to remove from starter pack', {safeMessage: err})
|
||||||
|
}
|
||||||
|
Toast.show(_(msg`Failed to add to starter pack`), {type: 'error'})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const {mutate: removeMembership} = useListMembershipRemoveMutation({
|
const {mutate: removeMembership, isPending: isPendingRemove} =
|
||||||
|
useListMembershipRemoveMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
Toast.show(_(msg`Removed from starter pack`))
|
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: () => {
|
onError: err => {
|
||||||
Toast.show(_(msg`Failed to remove from starter pack`), 'xmark')
|
if (!isNetworkError(err)) {
|
||||||
setIsPendingRefresh(false)
|
logger.error('Failed to remove from starter pack', {safeMessage: err})
|
||||||
|
}
|
||||||
|
Toast.show(_(msg`Failed to remove from starter pack`), {type: 'error'})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isPending = isPendingAdd || isPendingRemove
|
||||||
|
|
||||||
const handleToggleMembership = () => {
|
const handleToggleMembership = () => {
|
||||||
if (!starterPack.list?.uri || isPending) return
|
if (!starterPack.list?.uri || isPending) return
|
||||||
|
|
||||||
@@ -313,7 +298,6 @@ function StarterPackItem({
|
|||||||
} else {
|
} else {
|
||||||
if (!starterPackWithMembership.listItem?.uri) {
|
if (!starterPackWithMembership.listItem?.uri) {
|
||||||
console.error('Cannot remove: missing membership URI')
|
console.error('Cannot remove: missing membership URI')
|
||||||
setIsPendingRefresh(false)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
removeMembership({
|
removeMembership({
|
||||||
@@ -379,8 +363,9 @@ function StarterPackItem({
|
|||||||
label={isInPack ? _(msg`Remove`) : _(msg`Add`)}
|
label={isInPack ? _(msg`Remove`) : _(msg`Add`)}
|
||||||
color={isInPack ? 'secondary' : 'primary_subtle'}
|
color={isInPack ? 'secondary' : 'primary_subtle'}
|
||||||
size="tiny"
|
size="tiny"
|
||||||
disabled={isPendingRefresh}
|
disabled={isPending}
|
||||||
onPress={handleToggleMembership}>
|
onPress={handleToggleMembership}>
|
||||||
|
{isPending && <ButtonIcon icon={Loader} />}
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
{isInPack ? <Trans>Remove</Trans> : <Trans>Add</Trans>}
|
{isInPack ? <Trans>Remove</Trans> : <Trans>Add</Trans>}
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
import {
|
import {type QueryClient, useInfiniteQuery} from '@tanstack/react-query'
|
||||||
type AppBskyGraphGetActorStarterPacks,
|
|
||||||
type AppBskyGraphGetStarterPacksWithMembership,
|
|
||||||
} from '@atproto/api'
|
|
||||||
import {
|
|
||||||
type InfiniteData,
|
|
||||||
type QueryClient,
|
|
||||||
type QueryKey,
|
|
||||||
useInfiniteQuery,
|
|
||||||
} from '@tanstack/react-query'
|
|
||||||
|
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
|
|
||||||
@@ -28,13 +19,7 @@ export function useActorStarterPacksQuery({
|
|||||||
}) {
|
}) {
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
|
|
||||||
return useInfiniteQuery<
|
return useInfiniteQuery({
|
||||||
AppBskyGraphGetActorStarterPacks.OutputSchema,
|
|
||||||
Error,
|
|
||||||
InfiniteData<AppBskyGraphGetActorStarterPacks.OutputSchema>,
|
|
||||||
QueryKey,
|
|
||||||
string | undefined
|
|
||||||
>({
|
|
||||||
queryKey: RQKEY(did),
|
queryKey: RQKEY(did),
|
||||||
queryFn: async ({pageParam}: {pageParam?: string}) => {
|
queryFn: async ({pageParam}: {pageParam?: string}) => {
|
||||||
const res = await agent.app.bsky.graph.getActorStarterPacks({
|
const res = await agent.app.bsky.graph.getActorStarterPacks({
|
||||||
@@ -59,13 +44,7 @@ export function useActorStarterPacksWithMembershipsQuery({
|
|||||||
}) {
|
}) {
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
|
|
||||||
return useInfiniteQuery<
|
return useInfiniteQuery({
|
||||||
AppBskyGraphGetStarterPacksWithMembership.OutputSchema,
|
|
||||||
Error,
|
|
||||||
InfiniteData<AppBskyGraphGetStarterPacksWithMembership.OutputSchema>,
|
|
||||||
QueryKey,
|
|
||||||
string | undefined
|
|
||||||
>({
|
|
||||||
queryKey: RQKEY_WITH_MEMBERSHIP(did),
|
queryKey: RQKEY_WITH_MEMBERSHIP(did),
|
||||||
queryFn: async ({pageParam}: {pageParam?: string}) => {
|
queryFn: async ({pageParam}: {pageParam?: string}) => {
|
||||||
const res = await agent.app.bsky.graph.getStarterPacksWithMembership({
|
const res = await agent.app.bsky.graph.getStarterPacksWithMembership({
|
||||||
|
|||||||
@@ -14,12 +14,22 @@
|
|||||||
* -prf
|
* -prf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AtUri} from '@atproto/api'
|
import {
|
||||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
type AppBskyActorDefs,
|
||||||
|
type AppBskyGraphGetStarterPacksWithMembership,
|
||||||
|
AtUri,
|
||||||
|
} from '@atproto/api'
|
||||||
|
import {
|
||||||
|
type InfiniteData,
|
||||||
|
useMutation,
|
||||||
|
useQuery,
|
||||||
|
useQueryClient,
|
||||||
|
} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {RQKEY as LIST_MEMBERS_RQKEY} from '#/state/queries/list-members'
|
import {RQKEY as LIST_MEMBERS_RQKEY} from '#/state/queries/list-members'
|
||||||
import {useAgent, useSession} from '#/state/session'
|
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
|
// sanity limit is SANITY_PAGE_LIMIT*PAGE_SIZE total records
|
||||||
const SANITY_PAGE_LIMIT = 1000
|
const SANITY_PAGE_LIMIT = 1000
|
||||||
@@ -123,7 +133,7 @@ export function useListMembershipAddMutation({
|
|||||||
// -prf
|
// -prf
|
||||||
return res
|
return res
|
||||||
},
|
},
|
||||||
onSuccess: (data, variables) => {
|
onSuccess: async (data, variables) => {
|
||||||
// manually update the cache; a refetch is too expensive
|
// manually update the cache; a refetch is too expensive
|
||||||
let memberships = queryClient.getQueryData<ListMembersip[]>(RQKEY())
|
let memberships = queryClient.getQueryData<ListMembersip[]>(RQKEY())
|
||||||
if (memberships) {
|
if (memberships) {
|
||||||
@@ -151,6 +161,42 @@ export function useListMembershipAddMutation({
|
|||||||
queryKey: LIST_MEMBERS_RQKEY(variables.listUri),
|
queryKey: LIST_MEMBERS_RQKEY(variables.listUri),
|
||||||
})
|
})
|
||||||
}, 1e3)
|
}, 1e3)
|
||||||
|
|
||||||
|
// update WITH_MEMBERSHIPS query
|
||||||
|
const subject = await agent
|
||||||
|
.getProfile({actor: variables.actorDid})
|
||||||
|
.then(res => res.data)
|
||||||
|
|
||||||
|
if (subject) {
|
||||||
|
queryClient.setQueryData<
|
||||||
|
InfiniteData<AppBskyGraphGetStarterPacksWithMembership.OutputSchema>
|
||||||
|
>(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)
|
onSuccess?.(data)
|
||||||
},
|
},
|
||||||
onError,
|
onError,
|
||||||
@@ -206,6 +252,32 @@ export function useListMembershipRemoveMutation({
|
|||||||
queryKey: LIST_MEMBERS_RQKEY(variables.listUri),
|
queryKey: LIST_MEMBERS_RQKEY(variables.listUri),
|
||||||
})
|
})
|
||||||
}, 1e3)
|
}, 1e3)
|
||||||
|
|
||||||
|
queryClient.setQueryData<
|
||||||
|
InfiniteData<AppBskyGraphGetStarterPacksWithMembership.OutputSchema>
|
||||||
|
>(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)
|
onSuccess?.(data)
|
||||||
},
|
},
|
||||||
onError,
|
onError,
|
||||||
|
|||||||
Reference in New Issue
Block a user