mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
fix: complete dashboard hydration
This commit is contained in:
1 parent
03877a0971
commit
d07b9249c8
16 files changed
+468
-60
No files matched your search
@@ -39,21 +39,28 @@ const mockAccessRef: { value: AccountAccess } = {
|
||||
value: { accounts: [], active: null, canSwitch: false },
|
||||
};
|
||||
|
||||
const mockAccount = () => ({
|
||||
id: "delegate",
|
||||
username: "delegate",
|
||||
email: null,
|
||||
role: "USER",
|
||||
avatarUrl: null,
|
||||
modules: {},
|
||||
accountAccess: mockAccessRef.value,
|
||||
});
|
||||
|
||||
vi.mock("@/hooks/use-auth", () => ({
|
||||
useAuth: () => ({
|
||||
user: {
|
||||
id: "delegate",
|
||||
username: "delegate",
|
||||
email: null,
|
||||
role: "USER",
|
||||
avatarUrl: null,
|
||||
modules: {},
|
||||
accountAccess: mockAccessRef.value,
|
||||
},
|
||||
user: mockAccount(),
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
refetch: vi.fn(),
|
||||
}),
|
||||
// The card reads the mount-pinned accessor: it paints inside the dashboard's
|
||||
// streamed boundary, where reading the grants on the hydration render would
|
||||
// disagree with the server and cost the whole streamed tree. These cases are
|
||||
// about the MOUNTED render, so the accessor answers the account.
|
||||
useAccountOnceMounted: () => mockAccount(),
|
||||
}));
|
||||
|
||||
vi.mock("@/hooks/use-account-switch", () => ({
|
||||
|
||||
@@ -5,7 +5,7 @@ import { HeartHandshake, Loader2 } from "lucide-react";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { TileHeader } from "@/components/insights/tile-header";
|
||||
import { useAccountSwitch } from "@/hooks/use-account-switch";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useAccountOnceMounted } from "@/hooks/use-auth";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import {
|
||||
accountLabel,
|
||||
@@ -49,7 +49,12 @@ import {
|
||||
*/
|
||||
export function LookingAfterCard() {
|
||||
const { t } = useTranslations();
|
||||
const { user } = useAuth();
|
||||
// Withheld until mounted: this card sits inside the dashboard's streamed
|
||||
// boundary and renders nothing without grants. The shell has usually answered
|
||||
// `/api/auth/me` before the boundary hydrates, so reading the grants on the
|
||||
// hydration render paints a card where the server painted none — a mismatch
|
||||
// that discards the streamed dashboard. See `useAccountOnceMounted`.
|
||||
const user = useAccountOnceMounted();
|
||||
const switchAccount = useAccountSwitch();
|
||||
|
||||
const access = user?.accountAccess;
|
||||
|
||||
@@ -66,7 +66,15 @@ function renderRow(workout: WorkoutListEntry, sportName: string) {
|
||||
|
||||
export function RecentWorkoutsTile() {
|
||||
const { t, locale } = useTranslations();
|
||||
const { data, isLoading, isError, refetch } = useWorkouts({ limit: 3 });
|
||||
const { data, isError, refetch } = useWorkouts({ limit: 3 });
|
||||
|
||||
// A query's `isLoading` is not a hydration-safe branch: TanStack reports the
|
||||
// optimistic mount fetch on the client's very first render and cannot on the
|
||||
// server, so an `isLoading` gate renders one thing in the streamed HTML and
|
||||
// another in the hydration render — React #418, and the whole streamed
|
||||
// dashboard is discarded. Gate on what both sides can see instead: whether
|
||||
// the cell holds an answer yet.
|
||||
const showLoading = !data && !isError;
|
||||
const workouts = data?.workouts ?? [];
|
||||
|
||||
return (
|
||||
@@ -95,7 +103,7 @@ export function RecentWorkoutsTile() {
|
||||
}
|
||||
/>
|
||||
|
||||
{isLoading ? (
|
||||
{showLoading ? (
|
||||
// v1.4.43 W11-L6 — reserve roughly the loaded tile height
|
||||
// (3 workouts × ~3 rem rows + spacing) so the surrounding
|
||||
// dashboard layout doesn't reflow once the data lands.
|
||||
|
||||
Reference in new issue
Block a user