Skip to content

perf: show loading spinner during navigation#312

Merged
alexsapps merged 4 commits intomainfrom
alex/frontend-v2-nav-loading-indicator
Mar 1, 2026
Merged

perf: show loading spinner during navigation#312
alexsapps merged 4 commits intomainfrom
alex/frontend-v2-nav-loading-indicator

Conversation

@alexsapps
Copy link
Collaborator

@alexsapps alexsapps commented Mar 1, 2026

Summary by CodeRabbit

  • New Features
    • Added a reusable loading indicator (spinner + label) supporting inline and full-page layouts.
    • Added an activists section that streams content and enables client-side hydration.
  • Bug Fixes / Improvements
    • Converted a page to client-side Suspense with the new loading fallback for smoother rendering.
    • Navigation chapter switcher now fetches for all users and shows loading/error states instead of hiding for non-admins.

@alexsapps alexsapps requested a review from jakehobbs as a code owner March 1, 2026 01:39
@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

📝 Walkthrough

Walkthrough

Adds a Loading component and a server-side ActivistsSection that prefetches activist names; replaces HydrationBoundary/dehydrate SSR flow with a client Suspense boundary in the page; updates Navbar chapter switching to fetch unconditionally and handle loading/error states.

Changes

Cohort / File(s) Summary
Loading Component
frontend-v2/src/app/loading.tsx
New named export Loading and default RouteLoading render a spinner + accessible status label; supports label, inline, and className props; uses lucide-react Loader2 and cn utility.
Server Activists section
frontend-v2/src/app/example-server-component/activists-section.tsx
New async server component ActivistsSection that creates an ApiClient (using cookies), initializes a TanStack QueryClient, and prefetches activist names for hydration/dehydration.
Page — client Suspense conversion
frontend-v2/src/app/example-server-component/page.tsx
Replaced HydrationBoundary/dehydrate SSR flow with a client-side Suspense wrapping ActivistsSection and a Loading fallback; removed server-side prefetch imports and related hydration code.
Navbar / Chapter switcher
frontend-v2/src/components/nav.tsx
Removed admin-only gating; chapter data fetched for all users, added isLoading/isError handling and corresponding UI states; disable logic now checks data presence rather than admin flag.

Sequence Diagram(s)

sequenceDiagram
    participant Client as "Client Page"
    participant Suspense as "React Suspense"
    participant Activists as "ActivistsSection (Server)"
    participant ApiClient as "ApiClient"
    participant API as "External API"

    Client->>Suspense: Render ActivistsSection (client)
    Suspense->>Activists: Request server component render
    Activists->>ApiClient: instantiate (with cookies) & prefetch names
    ApiClient->>API: GET /activist-names
    API-->>ApiClient: activist names response
    ApiClient-->>Activists: return data
    Activists-->>Suspense: rendered output (dehydrated state)
    Suspense-->>Client: display ActivistsSection
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble keys and spin a light,
A tiny loader glows at night.
Server fetches names from far and near,
Suspense holds breath — then they appear.
Hooray, the page is ready — hop and cheer! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a loading spinner UI component and integrating it into the navigation flow to provide visual feedback during page transitions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch alex/frontend-v2-nav-loading-indicator

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

Copy link

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend-v2/src/app/loading.tsx`:
- Around line 1-12: The Loading component's formatting is not compliant with the
project's code style; run the formatter (make fmt) or apply the project's
Prettier/ESLint formatting rules to this component so the JSX and imports match
the repo style, then commit the updated file; locate the Loading function and
its use of Loader2 to ensure only formatting changes are made (no functional
edits) before pushing.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 812017d and 98283c6.

📒 Files selected for processing (1)
  • frontend-v2/src/app/loading.tsx

@alexsapps alexsapps force-pushed the alex/frontend-v2-nav-loading-indicator branch from 98283c6 to 5f60deb Compare March 1, 2026 01:52
Copy link

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend-v2/src/app/example-server-component/page.tsx`:
- Around line 26-31: ActivistsFallback duplicates the spinner/loading UI;
replace it by reusing the shared Loading component from loading.tsx: import the
existing Loading (or similarly named) component and render it with a label or
children "Loading activists..." instead of a new JSX fallback; if the shared
Loading component doesn't accept custom text, update its signature (e.g., add a
label or children prop) to accept and render the per-page message, preserve
existing accessibility props (aria-hidden, role) and the Loader2 usage inside
the shared component, then remove the ActivistsFallback declaration and any
local references to it.

In `@frontend-v2/src/app/loading.tsx`:
- Around line 5-13: The loader isn't announced to screen readers; update the
outer container (the div that wraps Loader2 and the "Loading page..." span) to
be a polite live region by adding role="status" and aria-live="polite" (and
aria-busy="true" while loading) so assistive tech will announce the change;
ensure the visible <span>Loading page...</span> remains available to screen
readers (or add a visually-hidden copy if you prefer) and keep the Loader2 icon
purely decorative (aria-hidden="true") to avoid duplicate announcements.

In `@frontend-v2/src/components/nav.tsx`:
- Around line 163-166: ChapterSwitcherFallback doesn’t announce the loading
state to assistive tech; update the root element rendered by the
ChapterSwitcherFallback component to include live-region semantics (e.g., add
aria-live="polite" and role="status", and optionally aria-atomic="true") so
screen readers will announce "Loading chapters..." when this transient UI
appears; locate the ChapterSwitcherFallback functional component and add those
attributes to the outer div (the one using buefyStyles['navbar-item']).
- Around line 134-137: The ChapterSwitcher can throw via useSuspenseQuery
(queryFn: apiClient.getChapterList), so wrap the component in a local error
boundary to prevent errors bubbling to the route; either add a React
ErrorBoundary component around <ChapterSwitcher> or use React Query's
<QueryErrorResetBoundary> with an error fallback UI and a reset handler that
calls reset() to retry the query and clears error state; ensure the Suspense
that currently wraps ChapterSwitcher remains for loading, but place the
ErrorBoundary/QueryErrorResetBoundary outside Suspense so fetch errors are
caught and a compact fallback (e.g., retry button or collapsed nav) is rendered
instead of blowing up the navbar.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98283c6 and 5f60deb.

📒 Files selected for processing (4)
  • frontend-v2/src/app/example-server-component/activists-section.tsx
  • frontend-v2/src/app/example-server-component/page.tsx
  • frontend-v2/src/app/loading.tsx
  • frontend-v2/src/components/nav.tsx

Copy link

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend-v2/src/components/nav.tsx`:
- Around line 134-157: The error UI for the chapter list disables
recovery—modify the useQuery call in this component to destructure refetch
(e.g., const { data, isLoading, isError, refetch } = useQuery({ queryKey:
[API_PATH.CHAPTER_LIST], queryFn: apiClient.getChapterList })) and replace the
terminal "Chapters unavailable" block with a retry UI that calls refetch when
clicked (a button labeled "Retry" or "Reload chapters"); keep the existing
role="alert" and provide accessible text/hints, optionally disable the button
while isLoading or show a small spinner using the same buefyStyles navbar-item
container to preserve layout.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f60deb and 3d954e1.

📒 Files selected for processing (3)
  • frontend-v2/src/app/example-server-component/page.tsx
  • frontend-v2/src/app/loading.tsx
  • frontend-v2/src/components/nav.tsx

Comment on lines +134 to 157
const { data, isLoading, isError } = useQuery({
queryKey: [API_PATH.CHAPTER_LIST],
queryFn: apiClient.getChapterList,
enabled: user.role === 'admin',
})

if (user.role !== 'admin') {
return null
if (isLoading) {
return (
<div
className={buefyStyles['navbar-item']}
role="status"
aria-live="polite"
>
<span className="text-sm text-neutral-500">Loading chapters...</span>
</div>
)
}

if (isError) {
return (
<div className={buefyStyles['navbar-item']} role="alert">
<span className="text-sm text-neutral-500">Chapters unavailable</span>
</div>
)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add a retry path for chapter-list fetch failures.

Line 151 returns a terminal error state (Chapters unavailable) with no recovery action. This blocks chapter switching for admins until a full page reload.

Suggested fix
-  const { data, isLoading, isError } = useQuery({
+  const { data, isLoading, isError, refetch } = useQuery({
     queryKey: [API_PATH.CHAPTER_LIST],
     queryFn: apiClient.getChapterList,
   })
@@
   if (isError) {
     return (
-      <div className={buefyStyles['navbar-item']} role="alert">
-        <span className="text-sm text-neutral-500">Chapters unavailable</span>
+      <div className={buefyStyles['navbar-item']} role="alert">
+        <span className="text-sm text-neutral-500">Chapters unavailable.</span>
+        <button
+          type="button"
+          onClick={() => void refetch()}
+          className="ml-2 text-sm text-primary underline"
+        >
+          Retry
+        </button>
       </div>
     )
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data, isLoading, isError } = useQuery({
queryKey: [API_PATH.CHAPTER_LIST],
queryFn: apiClient.getChapterList,
enabled: user.role === 'admin',
})
if (user.role !== 'admin') {
return null
if (isLoading) {
return (
<div
className={buefyStyles['navbar-item']}
role="status"
aria-live="polite"
>
<span className="text-sm text-neutral-500">Loading chapters...</span>
</div>
)
}
if (isError) {
return (
<div className={buefyStyles['navbar-item']} role="alert">
<span className="text-sm text-neutral-500">Chapters unavailable</span>
</div>
)
}
const { data, isLoading, isError, refetch } = useQuery({
queryKey: [API_PATH.CHAPTER_LIST],
queryFn: apiClient.getChapterList,
})
if (isLoading) {
return (
<div
className={buefyStyles['navbar-item']}
role="status"
aria-live="polite"
>
<span className="text-sm text-neutral-500">Loading chapters...</span>
</div>
)
}
if (isError) {
return (
<div className={buefyStyles['navbar-item']} role="alert">
<span className="text-sm text-neutral-500">Chapters unavailable.</span>
<button
type="button"
onClick={() => void refetch()}
className="ml-2 text-sm text-primary underline"
>
Retry
</button>
</div>
)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend-v2/src/components/nav.tsx` around lines 134 - 157, The error UI for
the chapter list disables recovery—modify the useQuery call in this component to
destructure refetch (e.g., const { data, isLoading, isError, refetch } =
useQuery({ queryKey: [API_PATH.CHAPTER_LIST], queryFn: apiClient.getChapterList
})) and replace the terminal "Chapters unavailable" block with a retry UI that
calls refetch when clicked (a button labeled "Retry" or "Reload chapters"); keep
the existing role="alert" and provide accessible text/hints, optionally disable
the button while isLoading or show a small spinner using the same buefyStyles
navbar-item container to preserve layout.

@alexsapps alexsapps merged commit 9f4b6d8 into main Mar 1, 2026
2 checks passed
@alexsapps alexsapps deleted the alex/frontend-v2-nav-loading-indicator branch March 1, 2026 02:32
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.

1 participant