Refactor lists to use new queries (#1875)

* Refactor lists queries to react-query

* Delete old lists-list model

* Implement list, list-members, and list-memberships react-queries

* Update CreateOrEditList modal

* First pass at my-follows and actor-autocomplete queries

* Update ListAddUserModal to use new queries, change to ListAddRemoveUsersModal

* Update UserAddRemoveLists modal

* Remove old TODO

* Fix indent, autocomplete query

* Add a todo

---------

Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
Paul Frazee
2023-11-12 12:45:25 -08:00
committed by GitHub
parent 05b728fffc
commit d9e0a927c1
25 changed files with 1303 additions and 1545 deletions
+190
View File
@@ -0,0 +1,190 @@
/**
* NOTE
*
* This query is a temporary solution to our lack of server API for
* querying user membership in an API. It is extremely inefficient.
*
* THIS SHOULD ONLY BE USED IN MODALS FOR MODIFYING A USER'S LIST MEMBERSHIP!
* Use the list-members query for rendering a list's members.
*
* It works by fetching *all* of the user's list item records and querying
* or manipulating that cache. For users with large lists, it will fall
* down completely, so be very conservative about how you use it.
*
* -prf
*/
import {AtUri} from '@atproto/api'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {useSession} from '../session'
import {RQKEY as LIST_MEMBERS_RQKEY} from './list-members'
// sanity limit is SANITY_PAGE_LIMIT*PAGE_SIZE total records
const SANITY_PAGE_LIMIT = 1000
const PAGE_SIZE = 100
// ...which comes 100,000k list members
export const RQKEY = () => ['list-memberships']
export interface ListMembersip {
membershipUri: string
listUri: string
actorDid: string
}
/**
* This API is dangerous! Read the note above!
*/
export function useDangerousListMembershipsQuery() {
const {agent, currentAccount} = useSession()
return useQuery<ListMembersip[]>({
queryKey: RQKEY(),
async queryFn() {
if (!currentAccount) {
return []
}
let cursor
let arr: ListMembersip[] = []
for (let i = 0; i < SANITY_PAGE_LIMIT; i++) {
const res = await agent.app.bsky.graph.listitem.list({
repo: currentAccount.did,
limit: PAGE_SIZE,
cursor,
})
arr = arr.concat(
res.records.map(r => ({
membershipUri: r.uri,
listUri: r.value.list,
actorDid: r.value.subject,
})),
)
cursor = res.cursor
if (!cursor) {
break
}
}
return arr
},
})
}
/**
* Returns undefined for pending, false for not a member, and string for a member (the URI of the membership record)
*/
export function getMembership(
memberships: ListMembersip[] | undefined,
list: string,
actor: string,
): string | false | undefined {
if (!memberships) {
return undefined
}
const membership = memberships.find(
m => m.listUri === list && m.actorDid === actor,
)
return membership ? membership.membershipUri : false
}
export function useListMembershipAddMutation() {
const {agent, currentAccount} = useSession()
const queryClient = useQueryClient()
return useMutation<
{uri: string; cid: string},
Error,
{listUri: string; actorDid: string}
>({
mutationFn: async ({listUri, actorDid}) => {
if (!currentAccount) {
throw new Error('Not logged in')
}
const res = await agent.app.bsky.graph.listitem.create(
{repo: currentAccount.did},
{
subject: actorDid,
list: listUri,
createdAt: new Date().toISOString(),
},
)
// TODO
// we need to wait for appview to update, but there's not an efficient
// query for that, so we use a timeout below
// -prf
return res
},
onSuccess(data, variables) {
// manually update the cache; a refetch is too expensive
let memberships = queryClient.getQueryData<ListMembersip[]>(RQKEY())
if (memberships) {
memberships = memberships
// avoid dups
.filter(
m =>
!(
m.actorDid === variables.actorDid &&
m.listUri === variables.listUri
),
)
.concat([
{
...variables,
membershipUri: data.uri,
},
])
queryClient.setQueryData(RQKEY(), memberships)
}
// invalidate the members queries (used for rendering the listings)
// use a timeout to wait for the appview (see above)
setTimeout(() => {
queryClient.invalidateQueries({
queryKey: LIST_MEMBERS_RQKEY(variables.listUri),
})
}, 1e3)
},
})
}
export function useListMembershipRemoveMutation() {
const {agent, currentAccount} = useSession()
const queryClient = useQueryClient()
return useMutation<
void,
Error,
{listUri: string; actorDid: string; membershipUri: string}
>({
mutationFn: async ({membershipUri}) => {
if (!currentAccount) {
throw new Error('Not logged in')
}
const membershipUrip = new AtUri(membershipUri)
await agent.app.bsky.graph.listitem.delete({
repo: currentAccount.did,
rkey: membershipUrip.rkey,
})
// TODO
// we need to wait for appview to update, but there's not an efficient
// query for that, so we use a timeout below
// -prf
},
onSuccess(data, variables) {
// manually update the cache; a refetch is too expensive
let memberships = queryClient.getQueryData<ListMembersip[]>(RQKEY())
if (memberships) {
memberships = memberships.filter(
m =>
!(
m.actorDid === variables.actorDid &&
m.listUri === variables.listUri
),
)
queryClient.setQueryData(RQKEY(), memberships)
}
// invalidate the members queries (used for rendering the listings)
// use a timeout to wait for the appview (see above)
setTimeout(() => {
queryClient.invalidateQueries({
queryKey: LIST_MEMBERS_RQKEY(variables.listUri),
})
}, 1e3)
},
})
}