Skip to content

fix: prevent dashboard hydration crash from undefined usageStats fields#3

Merged
Zernio-Elean merged 1 commit into
mainfrom
fix/dashboard-usage-stats-hydration-crash
May 11, 2026
Merged

fix: prevent dashboard hydration crash from undefined usageStats fields#3
Zernio-Elean merged 1 commit into
mainfrom
fix/dashboard-usage-stats-hydration-crash

Conversation

@Zernio-Elean

Copy link
Copy Markdown
Contributor

Summary

  • Dashboard at /dashboard (and every child route) crashed during hydration with Cannot read properties of undefined (reading 'toLocaleString').
  • Root cause: the {usageStats && (...)} guard in the dashboard layout only checked the root object, but render code accessed nested fields (usageStats.usage.uploads, usageStats.limits.uploads) that could be undefined.
  • Two corruption sources: stale shapes in localStorage (zustand persist) and partial responses from @getlatedev/node SDK (which declares every UsageStats field as optional).
  • Fixed by adding normalizeUsageStats() in src/stores/auth-store.ts, applied at both boundaries: setUsageStats (API entry) and onRehydrateStorage (localStorage entry).

Why this approach

  • Boundary validation, not parche por componente. A single normalizer in the store guarantees the shape — no ?. sprinkled across 6 render sites.
  • No render code changed. The existing usageStats && guards are now sufficient because the store contract is strong.
  • Defaults are honest: missing numbers coerce to 0, missing planName to "Free". NaN/Infinity/strings are rejected.
  • Diff: +33/-2 in one file, single concern.

What was verified (senior-debugger QA on the diff)

  • ✅ All 7 consumer sites of usageStats audited — no breaking changes
  • ✅ Hydration ordering preserved (normalize before setHasHydrated(true))
  • ✅ No-flash guard (if (!hasHydrated || !apiKey) return null) still works
  • ✅ No SSR/CSR mismatch risk (client-only store)
  • ✅ No other unguarded .toLocaleString() on dashboard-tree data
  • ✅ TypeScript: unknown input + nested Partial cast correctly express the shape

Known follow-ups (out of scope for this hotfix)

  • usageStats never refreshes after first login — stale data lingers until user re-enters API key. Should be a separate PR adding an on-mount refetch.
  • Product decision: when limits.uploads is missing, do we want to render "X / 0" (current) or "X / ∞" (treat missing as unlimited, default to -1)? Current behavior is strictly safer than the crash.
  • Unit test for normalizeUsageStats — repo has no test infrastructure visible yet.

Test plan

  • Manually clear latewiz-auth from localStorage, log in, verify dashboard loads
  • Manually corrupt latewiz-auth in localStorage (e.g. {"state":{"apiKey":"sk_x","usageStats":{"planName":"Pro"}}} missing limits/usage), reload /dashboard — should render without crash, plan info shows 0 / 0
  • Healthy API response renders correctly with real numbers
  • Repro from Crisp ticket session_0cebeb59-4bbd-4c21-ba6a-2309ff33fe1e (user bommaacc@gmail.com) no longer crashes

…ard hydration crash

The dashboard layout crashed during hydration with 'Cannot read properties
of undefined (reading toLocaleString)' because the usageStats guard only
checked the root object, while render code accessed nested fields like
usageStats.usage.uploads.toLocaleString().

Two corruption sources triggered this:
1. Stale shapes persisted in localStorage from older schema versions
2. Partial API responses from late.usage.getUsageStats() (the SDK
   declares every field as optional)

Fixed by adding a normalizeUsageStats() function applied at both
boundaries: setUsageStats (API entry) and onRehydrateStorage
(localStorage entry). Components keep their existing usageStats &&
guards; the store now guarantees the full shape so nested access is
safe.
@vercel

vercel Bot commented May 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
latewiz Ready Ready Preview, Comment May 11, 2026 5:17pm

Request Review

@Zernio-Elean Zernio-Elean merged commit bdfe4da into main May 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant