diff --git a/__tests__/hooks/useNotificationsQuery.test.tsx b/__tests__/hooks/useNotificationsQuery.test.tsx index 59f51bbdc8..b61f434be2 100644 --- a/__tests__/hooks/useNotificationsQuery.test.tsx +++ b/__tests__/hooks/useNotificationsQuery.test.tsx @@ -25,7 +25,9 @@ describe('useNotificationsQuery', () => { fetchNextPage: jest.fn(), hasNextPage: true, isLoading: false, - isLoadingNextPage: false + isLoadingNextPage: false, + isSuccess: true, + isError: false }); const { result } = renderHook(() => useNotificationsQuery({ identity: 'id', reverse: true })); expect(result.current.items.map(i => i.id)).toEqual([3,2,1]); @@ -34,7 +36,11 @@ describe('useNotificationsQuery', () => { }); it('returns empty when no data', () => { - useInfiniteQueryMock.mockReturnValue({ data: undefined }); + useInfiniteQueryMock.mockReturnValue({ + data: undefined, + isSuccess: false, + isError: false + }); const { result } = renderHook(() => useNotificationsQuery({ identity: 'id' })); expect(result.current.items).toEqual([]); expect(result.current.isInitialQueryDone).toBe(false); @@ -42,7 +48,9 @@ describe('useNotificationsQuery', () => { it('resets when identity changes', async () => { useInfiniteQueryMock.mockReturnValue({ - data: { pages: [{ notifications: [{ id: 1 }] }] } + data: { pages: [{ notifications: [{ id: 1 }] }] }, + isSuccess: true, + isError: false }); const { result, rerender } = renderHook( ({ identity }) => useNotificationsQuery({ identity }), @@ -50,7 +58,11 @@ describe('useNotificationsQuery', () => { ); expect(result.current.items).toHaveLength(1); - useInfiniteQueryMock.mockReturnValue({ data: undefined }); + useInfiniteQueryMock.mockReturnValue({ + data: undefined, + isSuccess: false, + isError: false + }); rerender({ identity: 'b' }); expect(result.current.items).toEqual([]); diff --git a/app/my-stream/notifications/page.tsx b/app/my-stream/notifications/page.tsx index 2736838eec..132abe46b9 100644 --- a/app/my-stream/notifications/page.tsx +++ b/app/my-stream/notifications/page.tsx @@ -21,7 +21,7 @@ export default async function NotificationsPage() { )?.value; if ( - notificationsFetched && + !notificationsFetched || +notificationsFetched < Time.now().toMillis() - 60000 ) { await prefetchAuthenticatedNotifications({ diff --git a/components/react-query-wrapper/ReactQueryWrapper.tsx b/components/react-query-wrapper/ReactQueryWrapper.tsx index b9485ea863..c3e0ca48f7 100644 --- a/components/react-query-wrapper/ReactQueryWrapper.tsx +++ b/components/react-query-wrapper/ReactQueryWrapper.tsx @@ -1194,7 +1194,7 @@ export default function ReactQueryWrapper({ Cookies.set([QueryKey.FEED_ITEMS].toString(), `${Time.now().toMillis()}`); }); - useQueryKeyListener([QueryKey.FEED_ITEMS], () => { + useQueryKeyListener([QueryKey.IDENTITY_NOTIFICATIONS], () => { Cookies.set( [QueryKey.IDENTITY_NOTIFICATIONS].toString(), `${Time.now().toMillis()}` diff --git a/hooks/useNotificationsQuery.tsx b/hooks/useNotificationsQuery.tsx index 3a9739612e..4aae11d6a7 100644 --- a/hooks/useNotificationsQuery.tsx +++ b/hooks/useNotificationsQuery.tsx @@ -1,10 +1,9 @@ "use client"; -import { useState, useEffect } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; import { commonApiFetch } from "@/services/api/common-api"; import { - TypedNotification, TypedNotificationsResponse, } from "@/types/feed.types"; import { ApiNotificationCause } from "@/generated/models/ApiNotificationCause"; @@ -79,24 +78,19 @@ export function useNotificationsQuery({ limit = "30", cause = null, }: UseNotificationsQueryProps) { - const queryClient = useQueryClient(); - - const [items, setItems] = useState([]); - const [isInitialQueryDone, setIsInitialQueryDone] = useState(false); + const prefetch = usePrefetchNotifications(); /** * OPTIONAL: Prefetch the first few pages of notifications. * This is similar to how `useMyStreamQuery` sets up prefetching. */ - queryClient.prefetchInfiniteQuery({ - queryKey: getIdentityNotificationsQueryKey(identity, limit, cause), - queryFn: ({ pageParam }: { pageParam?: number | null }) => - fetchNotifications({ limit, cause, pageParam }), - initialPageParam: null, - getNextPageParam: (lastPage) => lastPage.notifications.at(-1)?.id ?? null, - pages: 3, - staleTime: 60000, - }); + useEffect(() => { + if (!identity || activeProfileProxy) { + return; + } + + prefetch({ identity, limit, cause }); + }, [prefetch, identity, activeProfileProxy, limit, cause]); /** * Now the actual Infinite Query for notifications @@ -111,31 +105,20 @@ export function useNotificationsQuery({ staleTime: 60000, }); - useEffect(() => { - setItems([]); - setIsInitialQueryDone(false); - }, [identity, cause]); - - /** - * Flatten all pages and (optionally) reverse them. Store in local state. - */ - useEffect(() => { + const items = useMemo(() => { if (!query.data) { - return; + return []; } - let data: TypedNotification[] = ( + const data = ( query.data.pages as TypedNotificationsResponse[] ).flatMap((page) => page.notifications); - if (reverse) { - data = data.reverse(); - } - - setItems(data); - setIsInitialQueryDone(true); + return reverse ? [...data].reverse() : data; }, [query.data, reverse]); + const isInitialQueryDone = query.isSuccess || query.isError; + // Return everything the query provides, plus our flattened items & readiness indicator. return { ...query, @@ -147,26 +130,30 @@ export function useNotificationsQuery({ export function usePrefetchNotifications() { const queryClient = useQueryClient(); - return ({ - identity, - cause = null, - limit = "30", - }: { - identity: string | null; - cause?: ApiNotificationCause[] | null; - limit?: string; - }) => { - if (!identity) { - return; - } - queryClient.prefetchInfiniteQuery({ - queryKey: getIdentityNotificationsQueryKey(identity, limit, cause), - queryFn: ({ pageParam }: { pageParam?: number | null }) => - fetchNotifications({ limit, cause, pageParam }), - initialPageParam: null, - getNextPageParam: (lastPage) => lastPage.notifications.at(-1)?.id ?? null, - pages: 3, - staleTime: 60000, - }); - }; + return useCallback( + ({ + identity, + cause = null, + limit = "30", + }: { + identity: string | null; + cause?: ApiNotificationCause[] | null; + limit?: string; + }) => { + if (!identity) { + return; + } + queryClient.prefetchInfiniteQuery({ + queryKey: getIdentityNotificationsQueryKey(identity, limit, cause), + queryFn: ({ pageParam }: { pageParam?: number | null }) => + fetchNotifications({ limit, cause, pageParam }), + initialPageParam: null, + getNextPageParam: (lastPage) => + lastPage.notifications.at(-1)?.id ?? null, + pages: 3, + staleTime: 60000, + }); + }, + [queryClient] + ); }