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:
@@ -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)
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user