fix(users): SSR issue#421
Conversation
📝 WalkthroughWalkthroughUser form components migrated to React Query Suspense API. ChangesUser Form Suspense Migration
Sequence DiagramsequenceDiagram
participant EditPage as Edit Page<br/>(Suspense boundary)
participant UserForm as UserForm<br/>(coordinator)
participant EditUserForm as EditUserForm<br/>(edit fetch)
participant UserFormInner as UserFormInner<br/>(form & mutation)
participant ReactQuery as React Query<br/>(useSuspenseQuery)
EditPage->>UserForm: render with userId
UserForm->>ReactQuery: useSuspenseQuery(chapters)
ReactQuery-->>UserForm: chapters data
UserForm->>EditUserForm: delegate with userId
EditUserForm->>ReactQuery: useSuspenseQuery(user)
ReactQuery-->>EditUserForm: user data
EditUserForm->>UserFormInner: pass userId, user, chapters
UserFormInner-->>EditPage: render form
UserFormInner->>ReactQuery: useMutation(updateUser)
ReactQuery-->>UserFormInner: mutation result
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
frontend-v2/src/app/(authed)/users/new/page.tsx (1)
29-38: 💤 Low valueOptional: extract the shared loading fallback.
This fallback block is duplicated verbatim in
[id]/page.tsx. A small shared component (e.g.<FormLoadingFallback />) would keep the spinner/markup consistent across both pages.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend-v2/src/app/`(authed)/users/new/page.tsx around lines 29 - 38, Extract the duplicated fallback JSX into a new reusable component (e.g., FormLoadingFallback) that returns the spinner markup currently used as the Suspense fallback; then replace the inline fallback in this file's Suspense (surrounding UserForm) with <FormLoadingFallback /> and do the same replacement in the other page that duplicates it (the [id]/page.tsx Suspense fallback) so both pages share the same component and markup.frontend-v2/src/app/(authed)/users/user-form.tsx (1)
45-45: ⚡ Quick winReuse the chapter list item shape instead of redefining
Chapterlocally.
frontend-v2/src/lib/api.tsalready defines thegetChapterList()return shape viaChapterListResp.parse(resp).chapters(items are{ ChapterID: number; Name: string }), but it doesn’t currently export aChaptertype—onlyChapterOrganizeris exported. Export an inferredChaptertype fromlib/api(or derive it fromgetChapterList()’s return type) and reuse it inuser-form.tsx(and other duplicates likeuser-table.tsx).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend-v2/src/app/`(authed)/users/user-form.tsx at line 45, Replace the locally defined Chapter shape with a shared exported type from lib/api: add and export an inferred Chapter type in lib/api (derive it from ChapterListResp.parse(...).chapters or from getChapterList()'s return type) so it lives alongside the existing ChapterOrganizer export, then import that Chapter type into user-form.tsx and other duplicates (e.g., user-table.tsx) and remove the local "type Chapter = { ChapterID: number; Name: string }" definitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend-v2/src/app/`(authed)/users/user-form.tsx:
- Around line 295-320: Add a segment-scoped error boundary for the Suspense
queries used in UserForm/EditUserForm so client errors from useSuspenseQuery
render a recoverable UI instead of bubbling to the global Next.js error handler:
either add a route-level error.tsx under the same app segment (e.g.,
app/(authed)/users/error.tsx) that exports the Next.js error boundary UI, or
wrap the components that call useSuspenseQuery (functions EditUserForm and
UserForm or their parent page) in an ErrorBoundary from react-error-boundary (or
a simple class-based React error boundary) and provide a fallback UI that lets
users retry or navigate away; ensure the boundary encloses both the Suspense and
the useSuspenseQuery calls so errors thrown in apiClient.getUser or
apiClient.getChapterList are caught.
---
Nitpick comments:
In `@frontend-v2/src/app/`(authed)/users/new/page.tsx:
- Around line 29-38: Extract the duplicated fallback JSX into a new reusable
component (e.g., FormLoadingFallback) that returns the spinner markup currently
used as the Suspense fallback; then replace the inline fallback in this file's
Suspense (surrounding UserForm) with <FormLoadingFallback /> and do the same
replacement in the other page that duplicates it (the [id]/page.tsx Suspense
fallback) so both pages share the same component and markup.
In `@frontend-v2/src/app/`(authed)/users/user-form.tsx:
- Line 45: Replace the locally defined Chapter shape with a shared exported type
from lib/api: add and export an inferred Chapter type in lib/api (derive it from
ChapterListResp.parse(...).chapters or from getChapterList()'s return type) so
it lives alongside the existing ChapterOrganizer export, then import that
Chapter type into user-form.tsx and other duplicates (e.g., user-table.tsx) and
remove the local "type Chapter = { ChapterID: number; Name: string }"
definitions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d8b1f9c9-4f85-4f58-8c95-0128e42293c7
📒 Files selected for processing (3)
frontend-v2/src/app/(authed)/users/[id]/page.tsxfrontend-v2/src/app/(authed)/users/new/page.tsxfrontend-v2/src/app/(authed)/users/user-form.tsx
Switch to useSuspenseQuery and add a <Suspense fallback={...}> in page.tsx
Hydration error was caused by user-form.tsx:176-190:
```
{isLoading && (
<div className="flex items-center gap-2 text-muted-foreground text-sm">
...
</div>
)}
{!isLoading && (
<form ...>
```
Server renders the <div> (loading state). Client renders the <form>.
React sees different DOM trees → hydration error.
Why they differ: During Next.js SSR of UserForm (a client component),
the Providers component (see providers.tsx:40-42) creates a fresh, empty
QueryClient on the server (isServer = true → makeQueryClient() on every
call). Even though HydrationBoundary is supposed to inject data into
that client via useMemo before UserForm renders, in practice the
TanStack Query hooks in UserForm see isLoading: true during the server
pass — the queries are in a pending/fetching state.
On the client side, HydrationBoundary successfully rehydrates the
singleton browser QueryClient with the prefetched data, so isLoading is
false and the form renders.
Switch to useSuspenseQuery and add a in page.tsx
Hydration error was caused by user-form.tsx:176-190:
Server renders the
React sees different DOM trees → hydration error.
Why they differ: During Next.js SSR of UserForm (a client component),
the Providers component (see providers.tsx:40-42) creates a fresh, empty
QueryClient on the server (isServer = true → makeQueryClient() on every
call). Even though HydrationBoundary is supposed to inject data into
that client via useMemo before UserForm renders, in practice the
TanStack Query hooks in UserForm see isLoading: true during the server
pass — the queries are in a pending/fetching state.
On the client side, HydrationBoundary successfully rehydrates the
singleton browser QueryClient with the prefetched data, so isLoading is
false and the form renders.
Summary by CodeRabbit
New Features
Improvements