fix: read prefers-color-scheme via useSyncExternalStore (Category B, #200) - #367
Conversation
…200) TurtleEditor mirrored `window.matchMedia("(prefers-color-scheme: dark)")` into React state from an effect: it seeded "light", then corrected after the commit, then kept a manual change listener in sync. matchMedia is an external store, so it belongs in useSyncExternalStore. Adds `usePrefersDarkMode`, which subscribes to the media query directly. The value is already correct on the first committed render instead of light-first-then-corrected, and concurrent readers cannot tear. `getServerSnapshot` returns false, matching both the old initial value and the fact that the server cannot know the visitor's OS preference, so there is no hydration mismatch and no behaviour change. The hook also degrades to light when `matchMedia` is missing, which the previous inline `typeof window !== "undefined"` guard did implicitly. `react-hooks/set-state-in-effect`: 10 warnings → 9. Category B's other site, `useCollaborationStatus.ts:132`, is deliberately untouched — see the PR description. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Category B of #200 — browser-API subscriptions.
The one site I changed
TurtleEditor.tsx:110mirroredwindow.matchMedia("(prefers-color-scheme: dark)")into React state from an effect: seed"light", correct after the commit, then keep a manualchangelistener in sync.matchMediais an external store, which is exactly whatuseSyncExternalStoreexists for.New
lib/hooks/usePrefersDarkMode.tssubscribes to the media query directly. Three things get better:matchMediais absent, which the old inlinetypeof window !== "undefined"guard only did implicitly.getServerSnapshotreturnsfalse, which matches both the old initial value and the fact that the server can't know the visitor's OS preference — so no hydration mismatch and no behaviour change.The other site is deliberately untouched
#200 lists
lib/hooks/useCollaborationStatus.ts:132as the second Category B site, suggesting the WebSocket lifecycle be wrapped in a small store.That code is deleted by #154. Issue #140 is that the hook was pointing at the lint endpoint rather than a collaboration one, and #154's fix replaces the whole implementation with a stub:
I checked: that version contains zero
useState/useEffect, so the warning goes away with it, and #154 currently merges cleanly withdev. Building auseSyncExternalStorestore here would mean writing a store for code that is about to be removed, and would conflict with #154 for no benefit. Both #140 and #200 are v0.4.0, so there's no milestone reason to prefer one order — but #154 does strictly more, so it should go first.Category B therefore closes out as: this PR + #154.
Verification against the #365 base
react-hooks/set-state-in-effect: 10 → 9 warnings (and → 8 once refactor: decouple useCollaborationStatus from lint WebSocket endpoint #154 lands).npm run type-checkclean,npm run lint0 errors,npm run buildsucceeds.npx vitest --run— 160 files / 2759 tests pass. The 18 existingTurtleEditortests pass unchanged.usePrefersDarkMode: correct value on first render, light default, live response to preference changes, unsubscribe on unmount, and the missing-matchMediafallback.Scope
Partial progress on #200 — does not close it. Rule stays at
"warn".