fix(hydration): stop the mobile media query running during hydration (chat#1912 row 5) - #1916
Conversation
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
ChangesMobile query initialization
Estimated code review effort: 1 (Trivial) | ~5 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✨ 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.
No issues found across 2 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Auto-approved: Fixes hydration mismatch by deferring media query evaluation to after hydration. Adds tests verifying the fix.
Re-trigger cubic
…(chat#1912 row 5) usehooks-ts defaults useMediaQuery's initializeWithValue to true, which evaluates window.matchMedia during the first render — including the hydration render. The server has no matchMedia and renders the desktop branch, so on any viewport under 768px the client's first render disagreed: Header emitted an "Add Your Artist" button the server never sent. React threw a hydration mismatch (#418) and regenerated the entire tree on every page load, for signed -out and signed-in visitors alike. Confirmed by reproducing against a dev build, where React names the node: the diff points at Header's extra <button aria-label="Add a new artist">. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
74513e7 to
c702c10
Compare
Preview verification — 2026-07-30Preview https://chat-k92m5496q-recoup.vercel.app, built from head Rebased onto The viewport was set before each load, never after. This bug only exists below 768px — above it the server and client agree and nothing reproduces — and resizing after hydration proves nothing, because hydration has already happened. Results
Row 0 is the point of the run: the same browser, same viewport, same signed-out state throws #418 against production and is silent against this preview. Row 5 is the one that could have gone wrong — the fix defers the media-query result to an effect, so the risk was that the mobile UI never appears at all. It appears. What was actually wrongusehooks-ts defaults const [matches, setMatches] = useState(() => {
if (initializeWithValue) return getMatches(query); // runs during hydration
return defaultValue;
});The server has no Pinning it took three narrowing steps, recorded because the first two are reusable:
Suite 332 passed / 82 files after the rebase, No review findingsNo bot or human comments were left on this PR, so there was nothing to triage. |
Closes row 5 of chat#1912.
Why
Uncaught Error: Minified React error #418fires on every cold load, signed out and signed in, on/and/catalogs/{id}. React regenerates the entire tree on the client each time, so the whole app pays a full re-render on first paint.The error's args are
["HTML", ""], which in React 19'sthrowOnHydrationMismatchisfromText ? "text" : "HTML"— an element mismatch, not a text one. That ruled out the usual suspects (Dateformatting, locale,Math.random()), and SSR output was confirmed byte-identical across fetches, so the server was not the variable side.Reproducing against a dev build made React name the node:
The
+is client-only output.Header.tsxrenders that button behindisMobile && !isArtistSelected, anduseIsMobilewraps usehooks-tsuseMediaQuery, whoseinitializeWithValuedefaults totrue:On the server
getMatchesreturnsdefaultValue(false); on the client the hydration render already evaluatesmatchMedia, so at any viewport under 768px the two renders disagree and an extra<button>appears. Nothing about this is mobile-only in consequence: the mismatch regenerates the whole tree for that visitor.What changed
One option on
hooks/useIsMobile.tsx:initializeWithValue: false. The first render now matches the server, and the real viewport value arrives via the hook's existing layout effect — so the mobile UI still appears immediately, it just no longer contradicts the server's HTML.components/Sidebar/RecentChats/useRecentChats.tsuses a different hook (useMobileDetection) which is already effect-based and correct, so it needed no change.Verification
Reproduced and fixed against a local dev build at a 500px viewport:
/cold loadHydration failed because the server rendered HTML didn't match the client+ the<Header>diff above/catalogscold loadThe only console error remaining on localhost is Privy's
frame-ancestorsCSP rejection, which is expected off-domain and unrelated.Tests
hooks/__tests__/useIsMobile.test.tsx— 3 cases, TDD red→green. The first two were RED against the real defect (the hook calleduseMediaQuerywith no options object). They assert the hook does not evaluate the query during the hydration render, keeps the 768px breakpoint, and returns what the query reports.Full suite 285 passed / 76 files,
tsc --noEmitclean.Verification on preview
The issue's Works when script (cold, signed-out loads of
/,/catalogs,/catalogs/{id}, console clean of #418 / #423 / #425) will be re-run against the preview and posted as a PR comment.🤖 Generated with Claude Code
Summary by cubic
Prevent the mobile media query from running during hydration to stop React hydration mismatches (#418). Fixes chat#1912 row 5 and removes the full re-render on cold loads of
/and/catalogs.useMediaQuery("(max-width: 768px)", { initializeWithValue: false })fromusehooks-tsinuseIsMobileso the first client render matches SSR and no extra Header button appears on narrow viewports.useIsMobileto assert no query during hydration, the 768px breakpoint, and that the return mirrors the query.Written for commit c702c10. Summary will update on new commits.
Summary by CodeRabbit