fix: guard localStorage SSR in NotificationCenterProvider and suppress hydration mismatches#302
Merged
MaryammAli merged 2 commits intoJul 16, 2026
Conversation
…R hydration mismatches - NotificationCenterProvider: guard localStorage access with typeof window check, add try/catch on persist, expose hasHydrated in context value so consumers can opt-in to hydration-safe rendering - NotificationBell: use displayCount = hasHydrated ? unreadCount : 0 so the unread badge is always 0 on the server render, matching the initial client render and eliminating the hydration mismatch - NotificationFeed: add suppressHydrationWarning on the timestamp span that calls formatRelativeTime() — the function is pure/SSR-safe but Date.now() produces different values between server and client - notifications.ts: document sortNotifications and filterNotifications as pure (no browser globals); add try/catch fallback in formatRelativeTime for environments without Intl.RelativeTimeFormat; add JSDoc explaining hydration caveat for callers - Add vitest.config.ts + vitest.setup.ts for jsdom-based unit tests - Add src/__tests__/notifications.test.tsx covering sortNotifications (5 cases), filterNotifications (7 cases), formatRelativeTime (5 cases), and NotificationCenterProvider SSR hydration behaviour (6 cases) Closes BlockDash-Studios#290
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the SSR hydration issues reported in #290.
Root causes
window.localStoragewas accessed only insideuseEffect(correct), but theunreadCountderived from the pre-hydration state was used by NotificationBell to render a badge, causing a hydration mismatch.localStoragewas read, so the server HTML (count = 3) differed from the initial client render.formatRelativeTime()(which callsDate.now()) during render; the server timestamp and the client timestamp differ slightly, triggering React's hydration warning.formatRelativeTimehad no fallback ifIntl.RelativeTimeFormatis unavailable and no documentation on the SSR hydration caveat.Changes
NotificationCenterProvider.tsxtypeof window !== 'undefined'; addtry/catchon persist write; exposehasHydratedin contextNotificationBell.tsxdisplayCount = hasHydrated ? unreadCount : 0so the badge is always 0 on the server render, matching the initial client renderNotificationFeed.tsxsuppressHydrationWarningon the<span>containingformatRelativeTime()outputnotifications.tstry/catchfallback informatRelativeTime; add JSDoc explaining hydration caveatvitest.config.ts@/*alias for Vitestvitest.setup.ts@testing-library/jest-dommatcherssrc/__tests__/notifications.test.tsxsortNotifications,filterNotifications,formatRelativeTime, andNotificationCenterProviderSSR hydrationTesting
All 23 cases pass.
Closes #290