From 6bb3ee4bc7c446981f0fe2af885a04983f0217ee Mon Sep 17 00:00:00 2001 From: yohlo Date: Thu, 27 Aug 2026 14:14:03 -0700 Subject: [PATCH] fix: render local-timezone dates client-side only - started-tournament header and tournament list formatted times in the server timezone during ssr, mismatching hydration on every load --- .../components/started-tournament/header.tsx | 30 +++++++++++-------- .../components/tournament-list.tsx | 13 ++++---- src/locales/de/messages.po | 4 +-- src/locales/en/messages.po | 4 +-- src/locales/es/messages.po | 4 +-- src/locales/ja/messages.po | 4 +-- 6 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/features/tournaments/components/started-tournament/header.tsx b/src/features/tournaments/components/started-tournament/header.tsx index c9c38b7..5ee1095 100644 --- a/src/features/tournaments/components/started-tournament/header.tsx +++ b/src/features/tournaments/components/started-tournament/header.tsx @@ -2,11 +2,15 @@ import { Group, Stack, ThemeIcon, Text } from "@mantine/core"; import { Tournament } from "../../types"; import { CalendarIcon, MapPinIcon, TrophyIcon } from "@phosphor-icons/react"; import { useMemo } from "react"; +import { useMounted } from "@mantine/hooks"; import { Trans, useLingui } from "@lingui/react/macro"; import GlitchAvatar from "@/components/glitch-avatar"; const Header = ({ tournament }: { tournament: Tournament }) => { const { i18n } = useLingui(); + // Local-timezone text cannot be server-rendered: the pod formats in UTC and + // hydration mismatches on every load. + const mounted = useMounted(); const tournamentStart = useMemo( () => new Date(tournament.start_time), [tournament.start_time] @@ -48,18 +52,20 @@ const Header = ({ tournament }: { tournament: Tournament }) => { - - {tournamentStart.toLocaleDateString(i18n.locale, { - weekday: "short", - month: "short", - day: "numeric", - })}{" "} - at{" "} - {tournamentStart.toLocaleTimeString(i18n.locale, { - hour: "2-digit", - minute: "2-digit", - })} - + {mounted && ( + + {tournamentStart.toLocaleDateString(i18n.locale, { + weekday: "short", + month: "short", + day: "numeric", + })}{" "} + at{" "} + {tournamentStart.toLocaleTimeString(i18n.locale, { + hour: "2-digit", + minute: "2-digit", + })} + + )} diff --git a/src/features/tournaments/components/tournament-list.tsx b/src/features/tournaments/components/tournament-list.tsx index 3dce2be..bcda5ac 100644 --- a/src/features/tournaments/components/tournament-list.tsx +++ b/src/features/tournaments/components/tournament-list.tsx @@ -5,6 +5,7 @@ import { TournamentInfo } from "../types"; import { Fragment, useCallback } from "react"; import React from "react"; import { TrophyIcon, CalendarIcon, MapPinIcon } from "@phosphor-icons/react"; +import { useMounted } from "@mantine/hooks"; import { Trans, useLingui } from "@lingui/react/macro"; interface TournamentListProps { @@ -18,6 +19,7 @@ interface TournamentListItemProps { const TournamentListItem = React.memo(({ tournament }: TournamentListItemProps) => { const { i18n } = useLingui(); + const mounted = useMounted(); const startDate = tournament.start_time ? new Date(tournament.start_time) : null; return ( @@ -43,11 +45,12 @@ const TournamentListItem = React.memo(({ tournament }: TournamentListItemProps) - {startDate.toLocaleDateString(i18n.locale, { - month: 'short', - day: 'numeric', - year: 'numeric' - })} + {mounted && + startDate.toLocaleDateString(i18n.locale, { + month: 'short', + day: 'numeric', + year: 'numeric' + })} )} diff --git a/src/locales/de/messages.po b/src/locales/de/messages.po index e5f80db..76aba7a 100644 --- a/src/locales/de/messages.po +++ b/src/locales/de/messages.po @@ -93,7 +93,7 @@ msgstr "{0, plural, one {Spieler angemeldet} other {Spieler angemeldet}}" #. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", }) #. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", }) -#: src/features/tournaments/components/started-tournament/header.tsx:51 +#: src/features/tournaments/components/started-tournament/header.tsx:56 msgid "{0} at {1}" msgstr "{0} um {1}" @@ -1843,7 +1843,7 @@ msgstr "Noch keine Teams angemeldet" msgid "No teams found matching \"{search}\"" msgstr "Keine Teams gefunden, die zu \"{search}\" passen" -#: src/features/tournaments/components/tournament-list.tsx:95 +#: src/features/tournaments/components/tournament-list.tsx:98 msgid "No tournaments found" msgstr "Keine Turniere gefunden" diff --git a/src/locales/en/messages.po b/src/locales/en/messages.po index 6e438fc..4962b4a 100644 --- a/src/locales/en/messages.po +++ b/src/locales/en/messages.po @@ -93,7 +93,7 @@ msgstr "{0, plural, one {player enrolled} other {players enrolled}}" #. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", }) #. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", }) -#: src/features/tournaments/components/started-tournament/header.tsx:51 +#: src/features/tournaments/components/started-tournament/header.tsx:56 msgid "{0} at {1}" msgstr "{0} at {1}" @@ -1843,7 +1843,7 @@ msgstr "No teams enrolled yet" msgid "No teams found matching \"{search}\"" msgstr "No teams found matching \"{search}\"" -#: src/features/tournaments/components/tournament-list.tsx:95 +#: src/features/tournaments/components/tournament-list.tsx:98 msgid "No tournaments found" msgstr "No tournaments found" diff --git a/src/locales/es/messages.po b/src/locales/es/messages.po index 13b1649..e3c064f 100644 --- a/src/locales/es/messages.po +++ b/src/locales/es/messages.po @@ -93,7 +93,7 @@ msgstr "{0, plural, one {jugador inscrito} other {jugadores inscritos}}" #. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", }) #. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", }) -#: src/features/tournaments/components/started-tournament/header.tsx:51 +#: src/features/tournaments/components/started-tournament/header.tsx:56 msgid "{0} at {1}" msgstr "{0} a las {1}" @@ -1843,7 +1843,7 @@ msgstr "Aún no hay equipos inscritos" msgid "No teams found matching \"{search}\"" msgstr "No se encontraron equipos que coincidan con \"{search}\"" -#: src/features/tournaments/components/tournament-list.tsx:95 +#: src/features/tournaments/components/tournament-list.tsx:98 msgid "No tournaments found" msgstr "No se encontraron torneos" diff --git a/src/locales/ja/messages.po b/src/locales/ja/messages.po index 0c02a3c..2f6b479 100644 --- a/src/locales/ja/messages.po +++ b/src/locales/ja/messages.po @@ -93,7 +93,7 @@ msgstr "{0, plural, one {人のプレイヤーが登録済み} other {人のプ #. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", }) #. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", }) -#: src/features/tournaments/components/started-tournament/header.tsx:51 +#: src/features/tournaments/components/started-tournament/header.tsx:56 msgid "{0} at {1}" msgstr "{0} {1}" @@ -1843,7 +1843,7 @@ msgstr "まだチームが登録されていません" msgid "No teams found matching \"{search}\"" msgstr "「{search}」に一致するチームが見つかりません" -#: src/features/tournaments/components/tournament-list.tsx:95 +#: src/features/tournaments/components/tournament-list.tsx:98 msgid "No tournaments found" msgstr "トーナメントが見つかりません"