fix(ui): hydration mismatch su /tracking e /plan (date calcolate client-side)

I React errors #418/#423/#425 segnalati dal browser nel flow di salvataggio check-in: l'HTML server-rendered divergeva dal client-rendered, causando `Text content does not match`. La submit del check-in falliva silenziosamente.

Cause:
- `weight-entry-form.tsx` calcolava `todayISO()` (basato su `new Date()`) dentro `defaultValues` di `useForm`. Il render avviene sia SSR che hydration, e fra i due il valore può differire (anche di pochi ms attraverso la mezzanotte locale, oppure per fuso orario server vs client).
- `plan-week.tsx` con `todayDayOfWeek()` aveva lo stesso problema sul pannello macros.

Fix: il valore viene calcolato server-side nel rispettivo Server Component (`tracking/page.tsx`, `plan/page.tsx`) e passato come prop al Client Component. Stabile attraverso SSR e hydration perché serializzato nell'HTML iniziale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Opus 4.7 committed 2026-05-07 15:23:56 +02:00
1 parent f7f6410559
commit 221b567d45
4 files changed
+33 -18

No files matched your search

+8 -1
View File
@@ -38,11 +38,16 @@ export default async function TrackingPage({ params: { locale } }: { params: { l
fetchTodayCheckIn(),
]);
// Calcolato server-side per evitare hydration mismatch su WeightEntryForm.
const now = new Date();
const todayISO = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
return (
<TrackingPageContent
entries={entries}
profile={profile as ProfileLike}
todayCheckIn={todayCheckIn}
todayISO={todayISO}
/>
);
}
@@ -51,10 +56,12 @@ function TrackingPageContent({
entries,
profile,
todayCheckIn,
todayISO,
}: {
entries: Awaited<ReturnType<typeof fetchWeightEntries>>;
profile: ProfileLike;
todayCheckIn: Awaited<ReturnType<typeof fetchTodayCheckIn>>;
todayISO: string;
}) {
const t = useTranslations('Tracking');
const latest = entries[0] ?? null;
@@ -135,7 +142,7 @@ function TrackingPageContent({
<div className="relative grid gap-12 md:grid-cols-12">
<section className="animate-fade-up [animation-delay:300ms] md:col-span-7">
<p className="editorial-eyebrow mb-6">{t('newEntry')}</p>
<WeightEntryForm latest={latest} />
<WeightEntryForm latest={latest} todayISO={todayISO} />
</section>
<aside className="md:border-ink/15 animate-fade-up [animation-delay:420ms] md:col-span-5 md:border-l md:pl-10">
<p className="editorial-eyebrow mb-6">{t('history')}</p>