Restore post-thread caching behaviors (react-query refactor) (#2010)

* Rework resolve-did and resolve-uri queries to be smarter about cache reuse

* Precache handle resolutions

* Remove old unused code

* Load placeholder threads from the post-feed and notifications-feed queries

* Remove logs

* Fix bad ref

* Add loading spinners to the cache-loading thread view

* Scroll replies into view when loading threads

* Add caching within a thread

* Fix: dont show bottom border when the child spinner is active
This commit is contained in:
Paul Frazee 2023-11-27 17:41:30 -08:00 committed by GitHub
parent d4714baf13
commit f580d4daf0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 369 additions and 111 deletions

View file

@ -3,11 +3,17 @@ import {
AppBskyFeedPost,
AppBskyFeedGetPostThread,
} from '@atproto/api'
import {useQuery} from '@tanstack/react-query'
import {useQuery, useQueryClient, QueryClient} from '@tanstack/react-query'
import {getAgent} from '#/state/session'
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
import {STALE} from '#/state/queries'
import {
findPostInQueryData as findPostInFeedQueryData,
FeedPostSliceItem,
} from './post-feed'
import {findPostInQueryData as findPostInNotifsQueryData} from './notifications/feed'
import {precacheThreadPosts as precacheResolvedUris} from './resolve-uri'
export const RQKEY = (uri: string) => ['post-thread', uri]
type ThreadViewNode = AppBskyFeedGetPostThread.OutputSchema['thread']
@ -18,6 +24,8 @@ export interface ThreadCtx {
hasMore?: boolean
showChildReplyLine?: boolean
showParentReplyLine?: boolean
isParentLoading?: boolean
isChildLoading?: boolean
}
export type ThreadPost = {
@ -58,17 +66,44 @@ export type ThreadNode =
| ThreadUnknown
export function usePostThreadQuery(uri: string | undefined) {
const queryClient = useQueryClient()
return useQuery<ThreadNode, Error>({
staleTime: STALE.MINUTES.ONE,
queryKey: RQKEY(uri || ''),
async queryFn() {
const res = await getAgent().getPostThread({uri: uri!})
if (res.success) {
return responseToThreadNodes(res.data.thread)
const nodes = responseToThreadNodes(res.data.thread)
precacheResolvedUris(queryClient, nodes) // precache the handle->did resolution
return nodes
}
return {type: 'unknown', uri: uri!}
},
enabled: !!uri,
placeholderData: () => {
if (!uri) {
return undefined
}
{
const item = findPostInQueryData(queryClient, uri)
if (item) {
return threadNodeToPlaceholderThread(item)
}
}
{
const item = findPostInFeedQueryData(queryClient, uri)
if (item) {
return feedItemToPlaceholderThread(item)
}
}
{
const item = findPostInNotifsQueryData(queryClient, uri)
if (item) {
return postViewToPlaceholderThread(item)
}
}
return undefined
},
})
}
@ -178,3 +213,110 @@ function responseToThreadNodes(
return {type: 'unknown', uri: ''}
}
}
function findPostInQueryData(
queryClient: QueryClient,
uri: string,
): ThreadNode | undefined {
const queryDatas = queryClient.getQueriesData<ThreadNode>({
queryKey: ['post-thread'],
})
for (const [_queryKey, queryData] of queryDatas) {
if (!queryData) {
continue
}
for (const item of traverseThread(queryData)) {
if (item.uri === uri) {
return item
}
}
}
return undefined
}
function* traverseThread(node: ThreadNode): Generator<ThreadNode, void> {
if (node.type === 'post') {
if (node.parent) {
yield* traverseThread(node.parent)
}
yield node
if (node.replies?.length) {
for (const reply of node.replies) {
yield* traverseThread(reply)
}
}
}
}
function threadNodeToPlaceholderThread(
node: ThreadNode,
): ThreadNode | undefined {
if (node.type !== 'post') {
return undefined
}
return {
type: node.type,
_reactKey: node._reactKey,
uri: node.uri,
post: node.post,
record: node.record,
parent: undefined,
replies: undefined,
viewer: node.viewer,
ctx: {
depth: 0,
isHighlightedPost: true,
hasMore: false,
showChildReplyLine: false,
showParentReplyLine: false,
isParentLoading: !!node.record.reply,
isChildLoading: !!node.post.replyCount,
},
}
}
function feedItemToPlaceholderThread(item: FeedPostSliceItem): ThreadNode {
return {
type: 'post',
_reactKey: item.post.uri,
uri: item.post.uri,
post: item.post,
record: item.record,
parent: undefined,
replies: undefined,
viewer: item.post.viewer,
ctx: {
depth: 0,
isHighlightedPost: true,
hasMore: false,
showChildReplyLine: false,
showParentReplyLine: false,
isParentLoading: !!item.record.reply,
isChildLoading: !!item.post.replyCount,
},
}
}
function postViewToPlaceholderThread(
post: AppBskyFeedDefs.PostView,
): ThreadNode {
return {
type: 'post',
_reactKey: post.uri,
uri: post.uri,
post: post,
record: post.record as AppBskyFeedPost.Record, // validate in notifs
parent: undefined,
replies: undefined,
viewer: post.viewer,
ctx: {
depth: 0,
isHighlightedPost: true,
hasMore: false,
showChildReplyLine: false,
showParentReplyLine: false,
isParentLoading: !!(post.record as AppBskyFeedPost.Record).reply,
isChildLoading: !!post.replyCount,
},
}
}