create abstraction for persisting queries, make gcTime infinite

This commit is contained in:
Samuel Newman
2026-01-27 15:06:05 +02:00
parent 5b38f1744e
commit 8ad5d93129
4 changed files with 62 additions and 30 deletions
+22 -25
View File
@@ -8,11 +8,11 @@ import {
moderateFeedGenerator, moderateFeedGenerator,
RichText, RichText,
} from '@atproto/api' } from '@atproto/api'
import {t} from '@lingui/macro'
import { import {
type InfiniteData, type InfiniteData,
keepPreviousData, keepPreviousData,
type QueryClient, type QueryClient,
type QueryKey,
useInfiniteQuery, useInfiniteQuery,
useMutation, useMutation,
useQuery, useQuery,
@@ -22,7 +22,7 @@ import {
import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants' import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants'
import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles' import {sanitizeHandle} from '#/lib/strings/handles'
import {PERSISTED_QUERY_ROOT, STALE} from '#/state/queries' import {persist, STALE} from '#/state/queries'
import {RQKEY as listQueryKey} from '#/state/queries/list' import {RQKEY as listQueryKey} from '#/state/queries/list'
import {usePreferencesQuery} from '#/state/queries/preferences' import {usePreferencesQuery} from '#/state/queries/preferences'
import {useAgent, useSession} from '#/state/session' import {useAgent, useSession} from '#/state/session'
@@ -114,7 +114,7 @@ export function hydrateFeedGenerator(
avatar: view.avatar, avatar: view.avatar,
displayName: view.displayName displayName: view.displayName
? sanitizeDisplayName(view.displayName) ? sanitizeDisplayName(view.displayName)
: `Feed by ${sanitizeHandle(view.creator.handle, '@')}`, : t`Feed by ${sanitizeHandle(view.creator.handle, '@')}`,
description: new RichText({ description: new RichText({
text: view.description || '', text: view.description || '',
facets: (view.descriptionFacets || [])?.slice(), facets: (view.descriptionFacets || [])?.slice(),
@@ -155,7 +155,7 @@ export function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo {
creatorHandle: view.creator.handle, creatorHandle: view.creator.handle,
displayName: view.name displayName: view.name
? sanitizeDisplayName(view.name) ? sanitizeDisplayName(view.name)
: `User List by ${sanitizeHandle(view.creator.handle, '@')}`, : t`User List by ${sanitizeHandle(view.creator.handle, '@')}`,
contentMode: undefined, contentMode: undefined,
} }
} }
@@ -238,13 +238,7 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
) )
const lastPageCountRef = useRef(0) const lastPageCountRef = useRef(0)
const query = useInfiniteQuery< const query = useInfiniteQuery({
AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema,
Error,
InfiniteData<AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema>,
QueryKey,
string | undefined
>({
enabled: Boolean(moderationOpts) && options?.enabled !== false, enabled: Boolean(moderationOpts) && options?.enabled !== false,
queryKey: createGetPopularFeedsQueryKey(options), queryKey: createGetPopularFeedsQueryKey(options),
queryFn: async ({pageParam}) => { queryFn: async ({pageParam}) => {
@@ -261,7 +255,7 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
return res.data return res.data
}, },
initialPageParam: undefined, initialPageParam: undefined as string | undefined,
getNextPageParam: lastPage => lastPage.cursor, getNextPageParam: lastPage => lastPage.cursor,
select: useCallback( select: useCallback(
( (
@@ -418,11 +412,10 @@ const PWI_DISCOVER_FEED_STUB: SavedFeedSourceInfo = {
contentMode: undefined, contentMode: undefined,
} }
const createPinnedFeedInfosQueryKeyRoot = (...args: any[]) => [ const createPinnedFeedInfosQueryKeyRoot = (
PERSISTED_QUERY_ROOT, kind: 'pinned' | 'saved',
'feed-info', feedUris: string[],
...args, ) => ['feed-info', kind, feedUris]
]
export function usePinnedFeedsInfos() { export function usePinnedFeedsInfos() {
const {hasSession} = useSession() const {hasSession} = useSession()
@@ -431,12 +424,14 @@ export function usePinnedFeedsInfos() {
const pinnedItems = preferences?.savedFeeds.filter(feed => feed.pinned) ?? [] const pinnedItems = preferences?.savedFeeds.filter(feed => feed.pinned) ?? []
return useQuery({ return useQuery({
...persist(
createPinnedFeedInfosQueryKeyRoot(
'pinned',
pinnedItems.map(f => f.value),
),
),
staleTime: STALE.INFINITY, staleTime: STALE.INFINITY,
enabled: !isLoadingPrefs, enabled: !isLoadingPrefs,
queryKey: createPinnedFeedInfosQueryKeyRoot(
'pinned',
...pinnedItems.map(f => f.value),
),
queryFn: async () => { queryFn: async () => {
if (!hasSession) { if (!hasSession) {
return [PWI_DISCOVER_FEED_STUB] return [PWI_DISCOVER_FEED_STUB]
@@ -538,12 +533,14 @@ export function useSavedFeeds() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
return useQuery({ return useQuery({
...persist(
createPinnedFeedInfosQueryKeyRoot(
'saved',
savedItems.map(f => f.value),
),
),
staleTime: STALE.INFINITY, staleTime: STALE.INFINITY,
enabled: !isLoadingPrefs, enabled: !isLoadingPrefs,
queryKey: createPinnedFeedInfosQueryKeyRoot(
'saved',
...savedItems.map(f => f.value),
),
placeholderData: previousData => { placeholderData: previousData => {
return ( return (
previousData || { previousData || {
+31
View File
@@ -1,3 +1,5 @@
import {type QueryKey, queryOptions} from '@tanstack/react-query'
const SECOND = 1e3 const SECOND = 1e3
const MINUTE = SECOND * 60 const MINUTE = SECOND * 60
const HOUR = MINUTE * 60 const HOUR = MINUTE * 60
@@ -32,5 +34,34 @@ export const STALE = {
* *
* Also, only use this for queries that are safe to persist between * Also, only use this for queries that are safe to persist between
* app launches (like user preferences). * app launches (like user preferences).
*
* Note that for queries that are persisted, it is recommended to extend
* the `gcTime` to a longer duration, otherwise it'll get busted
*/ */
export const PERSISTED_QUERY_ROOT = 'PERSISTED' export const PERSISTED_QUERY_ROOT = 'PERSISTED'
export const PERSISTED_QUERY_GCTIME = Infinity
/**
* Sets up a query to be persisted automatically by the `PersistQueryClientProvider`.
* This automatically adjusts the query key, and sets the `gcTime` to be infinite.
*
* Be careful when using this, since it will change the query key and may
* break any cases where we call `invalidateQueries` or `refetchQueries`
* with the old key.
*
* Also, only use this for queries that are safe to persist between
* app launches (like user preferences).
*
* Use like this:
* ```ts
* useQuery({
* ...persist(['user', userId]),
* queryFn: () => fetchUser(userId),
* })
*/
export function persist<T extends QueryKey>(key: T) {
return queryOptions({
queryKey: [PERSISTED_QUERY_ROOT, ...key],
gcTime: PERSISTED_QUERY_GCTIME,
})
}
+3 -4
View File
@@ -3,7 +3,7 @@ import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {z} from 'zod' import {z} from 'zod'
import {MAX_LABELERS} from '#/lib/constants' import {MAX_LABELERS} from '#/lib/constants'
import {PERSISTED_QUERY_ROOT, STALE} from '#/state/queries' import {persist, STALE} from '#/state/queries'
import { import {
preferencesQueryKey, preferencesQueryKey,
usePreferencesQuery, usePreferencesQuery,
@@ -22,8 +22,7 @@ export const labelersInfoQueryKey = (dids: string[]) => [
dids.slice().sort(), dids.slice().sort(),
] ]
export const persistedLabelersDetailedInfoQueryKey = (dids: string[]) => [ const persistedLabelersDetailedInfoQueryKey = (dids: string[]) => [
PERSISTED_QUERY_ROOT,
'labelers-detailed-info', 'labelers-detailed-info',
dids, dids,
] ]
@@ -64,8 +63,8 @@ export function useLabelersInfoQuery({dids}: {dids: string[]}) {
export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) { export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) {
const agent = useAgent() const agent = useAgent()
return useQuery({ return useQuery({
...persist(persistedLabelersDetailedInfoQueryKey(dids)),
enabled: !!dids.length, enabled: !!dids.length,
queryKey: persistedLabelersDetailedInfoQueryKey(dids),
gcTime: 1000 * 60 * 60 * 6, // 6 hours gcTime: 1000 * 60 * 60 * 6, // 6 hours
staleTime: STALE.MINUTES.ONE, staleTime: STALE.MINUTES.ONE,
queryFn: async () => { queryFn: async () => {
+6 -1
View File
@@ -9,7 +9,11 @@ import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {PROD_DEFAULT_FEED} from '#/lib/constants' import {PROD_DEFAULT_FEED} from '#/lib/constants'
import {replaceEqualDeep} from '#/lib/functions' import {replaceEqualDeep} from '#/lib/functions'
import {getAge} from '#/lib/strings/time' import {getAge} from '#/lib/strings/time'
import {PERSISTED_QUERY_ROOT, STALE} from '#/state/queries' import {
PERSISTED_QUERY_GCTIME,
PERSISTED_QUERY_ROOT,
STALE,
} from '#/state/queries'
import { import {
DEFAULT_HOME_FEED_PREFS, DEFAULT_HOME_FEED_PREFS,
DEFAULT_LOGGED_OUT_PREFERENCES, DEFAULT_LOGGED_OUT_PREFERENCES,
@@ -40,6 +44,7 @@ export function usePreferencesQuery() {
structuralSharing: replaceEqualDeep, structuralSharing: replaceEqualDeep,
refetchOnWindowFocus: true, refetchOnWindowFocus: true,
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
gcTime: PERSISTED_QUERY_GCTIME,
queryFn: async () => { queryFn: async () => {
if (!agent.did) { if (!agent.did) {
return DEFAULT_LOGGED_OUT_PREFERENCES return DEFAULT_LOGGED_OUT_PREFERENCES