perf(dashboard): memoise the band math and the module-list parse

The dashboard recomputed the client band fallback on every render even when
the server snapshot supplied bands, and the module-list-prefs hook reparsed
its stored JSON into a fresh object each render, defeating downstream
memoisation. Wrap both in useMemo keyed on their real inputs.
This commit is contained in:
Marc-André Bombeck committed 2026-06-18 22:42:14 +02:00
1 parent 9eed8d9cfe
commit 3ec9aca798
2 files changed
+20 -7

No files matched your search

+14 -5
View File
@@ -532,11 +532,20 @@ export default function DashboardPage() {
// — it is pure + cheap — so a still-loading snapshot frame keeps real
// bands rather than blank charts; `serverBands ?? clientBands` then
// prefers the authoritative server numbers the moment they arrive.
const clientBands = buildDashboardBands({
dateOfBirth: user?.dateOfBirth ? new Date(user.dateOfBirth) : null,
gender: (user?.gender as "MALE" | "FEMALE" | null | undefined) ?? null,
heightCm: user?.heightCm ?? null,
});
// Inputs only change with profile facts, so memoise the band math — it
// ran on every dashboard render (and got discarded by `serverBands ??`
// in the snapshot steady state) before this. Keyed on the `user` object
// identity so the React Compiler can preserve the memo (a narrower
// property list trips `preserve-manual-memoization`).
const clientBands = useMemo(
() =>
buildDashboardBands({
dateOfBirth: user?.dateOfBirth ? new Date(user.dateOfBirth) : null,
gender: (user?.gender as "MALE" | "FEMALE" | null | undefined) ?? null,
heightCm: user?.heightCm ?? null,
}),
[user],
);
const bands = serverBands ?? clientBands;
const bpTargets = bands.bpTargets;
const weightRange = bands.weightRange;
+6 -2
View File
@@ -19,7 +19,7 @@
* block (the server default order is preserved for the tail).
*/
import { useCallback, useSyncExternalStore } from "react";
import { useCallback, useMemo, useSyncExternalStore } from "react";
export const MODULE_LIST_VIEWS = ["cards", "list"] as const;
export type ModuleListView = (typeof MODULE_LIST_VIEWS)[number];
@@ -150,7 +150,11 @@ export function useModuleListPrefs(module: ModuleListKey): {
}, [key]);
const raw = useSyncExternalStore(subscribe, getSnapshot, () => null);
const prefs = parseModuleListPrefs(raw);
// `useSyncExternalStore` returns a stable `raw` string when storage hasn't
// changed, so keying the parse on it gives downstream `applyOrder`
// memoisation a stable `prefs` reference instead of a fresh object + a
// JSON.parse on every render.
const prefs = useMemo(() => parseModuleListPrefs(raw), [raw]);
const write = useCallback(
(next: ModuleListPrefs) => {