Pinned feeds cards (#4526)
* Add lists support to FeedCard * Add useSavedFeeds query, similar to usePinnedFeedInfos * Integrate into Feeds screen * Fix alignment on mobile * Update usages * Add placeholder loading state * Handle no feeds state * Reuse previous data for placeholder * Staged loading * Improve staged loading * Use setQueryData approach to pre-caching * Add types for a little more safety * Fix precaching --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
This commit is contained in:
parent
cb37647949
commit
4d6787009c
6 changed files with 447 additions and 233 deletions
|
@ -9,20 +9,24 @@ import {
|
|||
} from '@atproto/api'
|
||||
import {
|
||||
InfiniteData,
|
||||
QueryClient,
|
||||
QueryKey,
|
||||
useInfiniteQuery,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {RQKEY as listQueryKey} from '#/state/queries/list'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {router} from '#/routes'
|
||||
import {FeedDescriptor} from './post-feed'
|
||||
import {precacheResolvedUri} from './resolve-uri'
|
||||
|
||||
export type FeedSourceFeedInfo = {
|
||||
type: 'feed'
|
||||
|
@ -201,6 +205,7 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
|||
const agent = useAgent()
|
||||
const limit = options?.limit || 10
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
// Make sure this doesn't invalidate unless really needed.
|
||||
const selectArgs = useMemo(
|
||||
|
@ -225,6 +230,13 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
|||
limit,
|
||||
cursor: pageParam,
|
||||
})
|
||||
|
||||
// precache feeds
|
||||
for (const feed of res.data.feeds) {
|
||||
const hydratedFeed = hydrateFeedGenerator(feed)
|
||||
precacheFeed(queryClient, hydratedFeed)
|
||||
}
|
||||
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
|
@ -449,3 +461,130 @@ export function usePinnedFeedsInfos() {
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
export type SavedFeedItem =
|
||||
| {
|
||||
type: 'feed'
|
||||
config: AppBskyActorDefs.SavedFeed
|
||||
view: AppBskyFeedDefs.GeneratorView
|
||||
}
|
||||
| {
|
||||
type: 'list'
|
||||
config: AppBskyActorDefs.SavedFeed
|
||||
view: AppBskyGraphDefs.ListView
|
||||
}
|
||||
| {
|
||||
type: 'timeline'
|
||||
config: AppBskyActorDefs.SavedFeed
|
||||
view: undefined
|
||||
}
|
||||
|
||||
export function useSavedFeeds() {
|
||||
const agent = useAgent()
|
||||
const {data: preferences, isLoading: isLoadingPrefs} = usePreferencesQuery()
|
||||
const savedItems = preferences?.savedFeeds ?? []
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useQuery({
|
||||
staleTime: STALE.INFINITY,
|
||||
enabled: !isLoadingPrefs,
|
||||
queryKey: [pinnedFeedInfosQueryKeyRoot, ...savedItems],
|
||||
placeholderData: previousData => {
|
||||
return (
|
||||
previousData || {
|
||||
count: savedItems.length,
|
||||
feeds: [],
|
||||
}
|
||||
)
|
||||
},
|
||||
queryFn: async () => {
|
||||
const resolvedFeeds = new Map<string, AppBskyFeedDefs.GeneratorView>()
|
||||
const resolvedLists = new Map<string, AppBskyGraphDefs.ListView>()
|
||||
|
||||
const savedFeeds = savedItems.filter(feed => feed.type === 'feed')
|
||||
const savedLists = savedItems.filter(feed => feed.type === 'list')
|
||||
|
||||
let feedsPromise = Promise.resolve()
|
||||
if (savedFeeds.length > 0) {
|
||||
feedsPromise = agent.app.bsky.feed
|
||||
.getFeedGenerators({
|
||||
feeds: savedFeeds.map(f => f.value),
|
||||
})
|
||||
.then(res => {
|
||||
res.data.feeds.forEach(f => {
|
||||
resolvedFeeds.set(f.uri, f)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const listsPromises = savedLists.map(list =>
|
||||
agent.app.bsky.graph
|
||||
.getList({
|
||||
list: list.value,
|
||||
limit: 1,
|
||||
})
|
||||
.then(res => {
|
||||
const listView = res.data.list
|
||||
resolvedLists.set(listView.uri, listView)
|
||||
}),
|
||||
)
|
||||
|
||||
await Promise.allSettled([feedsPromise, ...listsPromises])
|
||||
|
||||
resolvedFeeds.forEach(feed => {
|
||||
const hydratedFeed = hydrateFeedGenerator(feed)
|
||||
precacheFeed(queryClient, hydratedFeed)
|
||||
})
|
||||
resolvedLists.forEach(list => {
|
||||
precacheList(queryClient, list)
|
||||
})
|
||||
|
||||
const res: SavedFeedItem[] = savedItems.map(s => {
|
||||
if (s.type === 'timeline') {
|
||||
return {
|
||||
type: 'timeline',
|
||||
config: s,
|
||||
view: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: s.type,
|
||||
config: s,
|
||||
view:
|
||||
s.type === 'feed'
|
||||
? resolvedFeeds.get(s.value)
|
||||
: resolvedLists.get(s.value),
|
||||
}
|
||||
}) as SavedFeedItem[]
|
||||
|
||||
return {
|
||||
count: savedItems.length,
|
||||
feeds: res,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function precacheFeed(queryClient: QueryClient, hydratedFeed: FeedSourceInfo) {
|
||||
precacheResolvedUri(
|
||||
queryClient,
|
||||
hydratedFeed.creatorHandle,
|
||||
hydratedFeed.creatorDid,
|
||||
)
|
||||
queryClient.setQueryData<FeedSourceInfo>(
|
||||
feedSourceInfoQueryKey({uri: hydratedFeed.uri}),
|
||||
hydratedFeed,
|
||||
)
|
||||
}
|
||||
|
||||
function precacheList(
|
||||
queryClient: QueryClient,
|
||||
list: AppBskyGraphDefs.ListView,
|
||||
) {
|
||||
precacheResolvedUri(queryClient, list.creator.handle, list.creator.did)
|
||||
queryClient.setQueryData<AppBskyGraphDefs.ListView>(
|
||||
listQueryKey(list.uri),
|
||||
list,
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue