diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index 9af49361e0..e3238196a2 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -8,11 +8,11 @@ import { moderateFeedGenerator, RichText, } from '@atproto/api' +import {t} from '@lingui/macro' import { type InfiniteData, keepPreviousData, type QueryClient, - type QueryKey, useInfiniteQuery, useMutation, useQuery, @@ -22,7 +22,7 @@ import { import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants' import {sanitizeDisplayName} from '#/lib/strings/display-names' 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 {usePreferencesQuery} from '#/state/queries/preferences' import {useAgent, useSession} from '#/state/session' @@ -114,7 +114,7 @@ export function hydrateFeedGenerator( avatar: view.avatar, displayName: view.displayName ? sanitizeDisplayName(view.displayName) - : `Feed by ${sanitizeHandle(view.creator.handle, '@')}`, + : t`Feed by ${sanitizeHandle(view.creator.handle, '@')}`, description: new RichText({ text: view.description || '', facets: (view.descriptionFacets || [])?.slice(), @@ -155,7 +155,7 @@ export function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo { creatorHandle: view.creator.handle, displayName: view.name ? sanitizeDisplayName(view.name) - : `User List by ${sanitizeHandle(view.creator.handle, '@')}`, + : t`User List by ${sanitizeHandle(view.creator.handle, '@')}`, contentMode: undefined, } } @@ -238,13 +238,7 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) { ) const lastPageCountRef = useRef(0) - const query = useInfiniteQuery< - AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema, - Error, - InfiniteData, - QueryKey, - string | undefined - >({ + const query = useInfiniteQuery({ enabled: Boolean(moderationOpts) && options?.enabled !== false, queryKey: createGetPopularFeedsQueryKey(options), queryFn: async ({pageParam}) => { @@ -261,7 +255,7 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) { return res.data }, - initialPageParam: undefined, + initialPageParam: undefined as string | undefined, getNextPageParam: lastPage => lastPage.cursor, select: useCallback( ( @@ -418,11 +412,10 @@ const PWI_DISCOVER_FEED_STUB: SavedFeedSourceInfo = { contentMode: undefined, } -const createPinnedFeedInfosQueryKeyRoot = (...args: any[]) => [ - PERSISTED_QUERY_ROOT, - 'feed-info', - ...args, -] +const createPinnedFeedInfosQueryKeyRoot = ( + kind: 'pinned' | 'saved', + feedUris: string[], +) => ['feed-info', kind, feedUris] export function usePinnedFeedsInfos() { const {hasSession} = useSession() @@ -431,12 +424,14 @@ export function usePinnedFeedsInfos() { const pinnedItems = preferences?.savedFeeds.filter(feed => feed.pinned) ?? [] return useQuery({ + ...persist( + createPinnedFeedInfosQueryKeyRoot( + 'pinned', + pinnedItems.map(f => f.value), + ), + ), staleTime: STALE.INFINITY, enabled: !isLoadingPrefs, - queryKey: createPinnedFeedInfosQueryKeyRoot( - 'pinned', - ...pinnedItems.map(f => f.value), - ), queryFn: async () => { if (!hasSession) { return [PWI_DISCOVER_FEED_STUB] @@ -538,12 +533,14 @@ export function useSavedFeeds() { const queryClient = useQueryClient() return useQuery({ + ...persist( + createPinnedFeedInfosQueryKeyRoot( + 'saved', + savedItems.map(f => f.value), + ), + ), staleTime: STALE.INFINITY, enabled: !isLoadingPrefs, - queryKey: createPinnedFeedInfosQueryKeyRoot( - 'saved', - ...savedItems.map(f => f.value), - ), placeholderData: previousData => { return ( previousData || { diff --git a/src/state/queries/index.ts b/src/state/queries/index.ts index 666e07a453..a7b744ee62 100644 --- a/src/state/queries/index.ts +++ b/src/state/queries/index.ts @@ -1,3 +1,5 @@ +import {type QueryKey, queryOptions} from '@tanstack/react-query' + const SECOND = 1e3 const MINUTE = SECOND * 60 const HOUR = MINUTE * 60 @@ -32,5 +34,34 @@ export const STALE = { * * Also, only use this for queries that are safe to persist between * 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_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(key: T) { + return queryOptions({ + queryKey: [PERSISTED_QUERY_ROOT, ...key], + gcTime: PERSISTED_QUERY_GCTIME, + }) +} diff --git a/src/state/queries/labeler.ts b/src/state/queries/labeler.ts index d40f077b15..95f77623bd 100644 --- a/src/state/queries/labeler.ts +++ b/src/state/queries/labeler.ts @@ -3,7 +3,7 @@ import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {z} from 'zod' import {MAX_LABELERS} from '#/lib/constants' -import {PERSISTED_QUERY_ROOT, STALE} from '#/state/queries' +import {persist, STALE} from '#/state/queries' import { preferencesQueryKey, usePreferencesQuery, @@ -22,8 +22,7 @@ export const labelersInfoQueryKey = (dids: string[]) => [ dids.slice().sort(), ] -export const persistedLabelersDetailedInfoQueryKey = (dids: string[]) => [ - PERSISTED_QUERY_ROOT, +const persistedLabelersDetailedInfoQueryKey = (dids: string[]) => [ 'labelers-detailed-info', dids, ] @@ -64,8 +63,8 @@ export function useLabelersInfoQuery({dids}: {dids: string[]}) { export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) { const agent = useAgent() return useQuery({ + ...persist(persistedLabelersDetailedInfoQueryKey(dids)), enabled: !!dids.length, - queryKey: persistedLabelersDetailedInfoQueryKey(dids), gcTime: 1000 * 60 * 60 * 6, // 6 hours staleTime: STALE.MINUTES.ONE, queryFn: async () => { diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts index 5d30e2fb41..58df94fcc3 100644 --- a/src/state/queries/preferences/index.ts +++ b/src/state/queries/preferences/index.ts @@ -9,7 +9,11 @@ import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {PROD_DEFAULT_FEED} from '#/lib/constants' import {replaceEqualDeep} from '#/lib/functions' 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 { DEFAULT_HOME_FEED_PREFS, DEFAULT_LOGGED_OUT_PREFERENCES, @@ -40,6 +44,7 @@ export function usePreferencesQuery() { structuralSharing: replaceEqualDeep, refetchOnWindowFocus: true, queryKey: preferencesQueryKey, + gcTime: PERSISTED_QUERY_GCTIME, queryFn: async () => { if (!agent.did) { return DEFAULT_LOGGED_OUT_PREFERENCES