Skip to content

Notifications first load flickering#1526

Merged
simo6529 merged 7 commits intomainfrom
notifications-first-load-flickering
Oct 9, 2025
Merged

Notifications first load flickering#1526
simo6529 merged 7 commits intomainfrom
notifications-first-load-flickering

Conversation

@simo6529
Copy link
Copy Markdown
Collaborator

@simo6529 simo6529 commented Oct 9, 2025

Summary by CodeRabbit

  • Bug Fixes

    • Notifications now refresh when no cache exists or when cached data is older than 60s, keeping lists and badges up to date.
    • Smoother initial notification load to reduce flicker and duplicate entries.
    • Notification indicators and timestamps stay better in sync during browsing sessions.
  • Improvements

    • Background prefetching and timestamp updates are now tied more reliably to notifications, improving badge and timestamp accuracy.

Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 9, 2025

Walkthrough

Prefetch condition changed to run when no cache or cache older than 60s; query-key listener now observes IDENTITY_NOTIFICATIONS (cookie timestamp updated accordingly); useNotificationsQuery refactored to derive items and isInitialQueryDone (returned alongside original query props); tests updated to include query status flags.

Changes

Cohort / File(s) Summary of Changes
Notifications prefetch condition
app/my-stream/notifications/page.tsx
Prefetch now triggers when there is no cached notifications value or the cached value is older than 60 seconds; previous behavior only prefetched when a cached value existed and was stale. No public API changes.
Query key listener update
components/react-query-wrapper/ReactQueryWrapper.tsx
Changed observed query key from FEED_ITEMS to IDENTITY_NOTIFICATIONS in useQueryKeyListener; Cookies.set now uses IDENTITY_NOTIFICATIONS. Control flow otherwise unchanged.
Notifications hook refactor & API
hooks/useNotificationsQuery.tsx
Replaced local state with memoized items derived from query data; introduced isInitialQueryDone derived from query status; moved prefetch into usePrefetchNotifications and invoked via effect when identity is present and not an activeProfileProxy; hook now returns items: TypedNotification[] and isInitialQueryDone: boolean alongside original query props.
Tests updated for query status
__tests__/hooks/useNotificationsQuery.test.tsx
Mocks extended to include isSuccess/isError flags and adjusted data presence/absence across tests; minor trailing-comma formatting.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Page as Notifications Page
  participant Cache as Query Cache
  participant API as Server/API
  participant RQW as ReactQueryWrapper
  participant Hook as useNotificationsQuery

  Note over Page,Cache: User opens notifications page
  User->>Page: Navigate to notifications
  Page->>Cache: Check notifications cache + timestamp
  alt No cache OR stale (>60s)
    Page->>API: Prefetch notifications
    API-->>Cache: Store fresh notifications
  else Fresh cache
    Page->>Cache: Use cached data
  end

  Note over Cache,RQW: Query key update observed
  Cache-->>RQW: IDENTITY_NOTIFICATIONS updated
  RQW->>RQW: Cookies.set(timestamp for IDENTITY_NOTIFICATIONS)

  Note over Hook,Cache: Hook derives view model
  Hook->>Cache: Read query result
  Hook->>Hook: items = memoized(flatten(query.data))
  Hook->>Hook: isInitialQueryDone = isSuccess OR isError
  Hook-->>Page: Return { ...query, items, isInitialQueryDone }
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • ragnep

Poem

I hop through caches, quick and keen,
If stale or missing, I prefetch the scene.
A cookie ticks for IDENTITY news,
Hooks flatten items, no state to lose.
Paws tap keys, updates on the run—hooray! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title “Notifications first load flickering” describes the symptom but does not clearly state what change or fix this pull request introduces, nor is it formatted as a complete sentence summarizing the main modification. It lacks a verb and does not convey the action taken to address the issue. Please revise the title to explicitly summarize the change, for example “Fix notifications flicker on first load by adjusting prefetch logic,” so that reviewers can immediately understand the primary update.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch notifications-first-load-flickering

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c64c66d and c0bacad.

📒 Files selected for processing (1)
  • hooks/useNotificationsQuery.tsx (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Follow existing code style and naming conventions

Files:

  • hooks/useNotificationsQuery.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • hooks/useNotificationsQuery.tsx
🧬 Code graph analysis (1)
hooks/useNotificationsQuery.tsx (1)
types/feed.types.ts (1)
  • TypedNotificationsResponse (145-148)
🔇 Additional comments (4)
hooks/useNotificationsQuery.tsx (4)

81-93: LGTM!

The prefetch logic correctly leverages a stable memoized callback and guards against unnecessary prefetches. The dependency array is complete and the approach should eliminate flickering by ensuring data is available before the component renders.


108-118: LGTM!

The derived items computation using useMemo is cleaner than manual state management. The logic correctly handles empty data, flattens pages, and applies optional reversal. Dependencies are appropriate.


120-120: LGTM!

Deriving isInitialQueryDone from query status (isSuccess || isError) is more robust than manual state tracking and correctly indicates when the initial fetch attempt has completed.


130-159: LGTM!

The extracted usePrefetchNotifications hook is well-designed: the callback is stable (depends only on queryClient), parameters match the main query for consistency, and prefetching 3 pages should effectively eliminate flickering.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
hooks/useNotificationsQuery.tsx (1)

88-96: Wrap client-side prefetch in useEffect
The call to queryClient.prefetchInfiniteQuery inside useNotificationsQuery runs on every render—move it into a useEffect with [identity, limit, cause] so it only fires when those change.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 620a27b and 23f6b90.

📒 Files selected for processing (3)
  • app/my-stream/notifications/page.tsx (1 hunks)
  • components/react-query-wrapper/ReactQueryWrapper.tsx (1 hunks)
  • hooks/useNotificationsQuery.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Follow existing code style and naming conventions

Files:

  • components/react-query-wrapper/ReactQueryWrapper.tsx
  • app/my-stream/notifications/page.tsx
  • hooks/useNotificationsQuery.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • components/react-query-wrapper/ReactQueryWrapper.tsx
  • app/my-stream/notifications/page.tsx
  • hooks/useNotificationsQuery.tsx
{app,pages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

Use NextJS features that match the current version

Files:

  • app/my-stream/notifications/page.tsx
app/**

📄 CodeRabbit inference engine (AGENTS.md)

All new Next.js pages must be created under the app/ directory

Files:

  • app/my-stream/notifications/page.tsx
app/**/{page,layout}.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Routes in app/ should export a generateMetadata function using getAppMetadata

Files:

  • app/my-stream/notifications/page.tsx
🧬 Code graph analysis (2)
components/react-query-wrapper/ReactQueryWrapper.tsx (1)
hooks/useQueryKeyListener.ts (1)
  • useQueryKeyListener (6-27)
hooks/useNotificationsQuery.tsx (1)
types/feed.types.ts (2)
  • TypedNotification (135-143)
  • TypedNotificationsResponse (145-148)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
hooks/useNotificationsQuery.tsx (3)

3-3: LGTM!

The useMemo import is correctly added to support the new memoized items derivation below.


111-121: LGTM! Clean refactor to derived state.

Replacing useState with useMemo eliminates the synchronization complexity of useEffect and ensures items are always derived from the current query data. The reverse logic correctly creates a new array when needed.


123-123: Confirm disabled-query readiness
The isInitialQueryDone flag is false when enabled: false (e.g., no identity or activeProfileProxy is true). Verify that downstream components (e.g. Notifications) handle loader vs. no-items rendering correctly in this scenario.

app/my-stream/notifications/page.tsx (1)

23-26: LGTM! Fixes first-load flickering.

The updated condition now prefetches when there's no cached value (!notificationsFetched) or when stale, addressing the first-load flickering mentioned in the PR title. Previously, prefetch only occurred if a cached value existed and was stale, missing the initial-load case.

components/react-query-wrapper/ReactQueryWrapper.tsx (2)

1197-1202: LGTM! Correctly tracks IDENTITY_NOTIFICATIONS.

The listener now tracks IDENTITY_NOTIFICATIONS query updates and sets the corresponding cookie timestamp, which is read in page.tsx (line 19-21) to determine when to prefetch. The implementation is consistent with the existing pattern.


1193-1195: Remove or confirm necessity of FEED_ITEMS listener

No reads of the FEED_ITEMS cookie were found beyond this Cookies.set call. If this listener is no longer needed, remove it; otherwise, clarify why it must remain.

Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
__tests__/hooks/useNotificationsQuery.test.tsx (1)

39-43: Consider adding an error state test case.

While this test correctly verifies the in-progress state (isSuccess: false, isError: false), consider adding a test case where isError: true to verify that isInitialQueryDone becomes true even when the query fails.

Example test case:

it('marks initial query as done on error', () => {
  useInfiniteQueryMock.mockReturnValue({
    data: undefined,
    isSuccess: false,
    isError: true,
    error: new Error('Network error')
  });
  const { result } = renderHook(() => useNotificationsQuery({ identity: 'id' }));
  expect(result.current.items).toEqual([]);
  expect(result.current.isInitialQueryDone).toBe(true);
});
hooks/useNotificationsQuery.tsx (2)

88-102: Consider reducing duplication with usePrefetchNotifications.

The prefetch configuration here duplicates the logic in usePrefetchNotifications (lines 154-162). Consider refactoring to reuse the prefetch function:

  const queryClient = useQueryClient();
+ const prefetch = usePrefetchNotifications();

  useEffect(() => {
    if (!identity || activeProfileProxy) {
      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,
-   });
+   prefetch({ identity, limit, cause });
- }, [queryClient, identity, activeProfileProxy, limit, cause]);
+ }, [prefetch, identity, activeProfileProxy, limit, cause]);

117-127: LGTM! Optional: Simplify the type cast.

The memoized derivation is correct and efficient. The empty array cast on line 119 is redundant since TypeScript can infer the type from the return type, but it doesn't hurt.

Optional simplification:

  const items = useMemo(() => {
    if (!query.data) {
-     return [] as TypedNotification[];
+     return [];
    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23f6b90 and c64c66d.

📒 Files selected for processing (2)
  • __tests__/hooks/useNotificationsQuery.test.tsx (2 hunks)
  • hooks/useNotificationsQuery.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Follow existing code style and naming conventions

Files:

  • hooks/useNotificationsQuery.tsx
  • __tests__/hooks/useNotificationsQuery.test.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • hooks/useNotificationsQuery.tsx
  • __tests__/hooks/useNotificationsQuery.test.tsx
__tests__/**

📄 CodeRabbit inference engine (tests/AGENTS.md)

Place Jest test suites under the __tests__ directory mirroring source folders (e.g., components, contexts, hooks, utils)

Files:

  • __tests__/hooks/useNotificationsQuery.test.tsx
{**/__tests__/**,**/*.test.{ts,tsx}}

📄 CodeRabbit inference engine (AGENTS.md)

{**/__tests__/**,**/*.test.{ts,tsx}}: If coverage on a modified file is below 80%, add meaningful tests to raise it to at least 80%
Mock external dependencies and APIs in tests

Files:

  • __tests__/hooks/useNotificationsQuery.test.tsx
**/__tests__/**

📄 CodeRabbit inference engine (AGENTS.md)

Place tests in __tests__/ directories when organizing standalone test suites

Files:

  • __tests__/hooks/useNotificationsQuery.test.tsx
**/*.test.tsx

📄 CodeRabbit inference engine (AGENTS.md)

When co-locating tests with components, name them ComponentName.test.tsx

Files:

  • __tests__/hooks/useNotificationsQuery.test.tsx
🧬 Code graph analysis (2)
hooks/useNotificationsQuery.tsx (2)
components/navigation/BottomNavigation.tsx (1)
  • items (16-68)
types/feed.types.ts (2)
  • TypedNotification (135-143)
  • TypedNotificationsResponse (145-148)
__tests__/hooks/useNotificationsQuery.test.tsx (1)
hooks/useNotificationsQuery.tsx (1)
  • useNotificationsQuery (75-137)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
__tests__/hooks/useNotificationsQuery.test.tsx (2)

28-30: LGTM!

The mock correctly includes isSuccess and isError flags that align with the updated implementation where isInitialQueryDone is derived from query.isSuccess || query.isError.


51-53: LGTM!

The test correctly simulates identity change behavior where the new query starts in an incomplete state (isSuccess: false, isError: false), causing isInitialQueryDone to reset to false and items to reset to an empty array.

Also applies to: 61-65

hooks/useNotificationsQuery.tsx (2)

3-3: LGTM!

The useMemo import is necessary for the memoized items derivation implemented below.


129-130: LGTM!

The derived isInitialQueryDone correctly identifies when the initial query completes (either with success or error). This pattern is cleaner than explicit state tracking and ensures the value stays in sync with the query state.

ragnep
ragnep previously approved these changes Oct 9, 2025
Signed-off-by: Simo <simo@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Oct 9, 2025

@simo6529 simo6529 merged commit 5f67fc1 into main Oct 9, 2025
8 checks passed
@simo6529 simo6529 deleted the notifications-first-load-flickering branch October 9, 2025 08:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants