Frontend SPA crashes on boot with React #185 (max update depth) — object-returning Zustand selector in AuthInitializer; UI never renders #49

Closed
opened 2026-08-14 11:13:36 +00:00 by Mark · 0 comments
Owner

Summary

The deployed SPA crashes on boot before rendering anything — the root element stays empty and no login form ever appears. This blocks the entire UI, including the Settings page's encryption-at-rest controls ("Encrypt backups at rest" Switch + Encryption Key input).

Root cause

src/main.tsx:14-15:

const { logout } = useAuthStore(state => ({
  logout: state.logout,
  // ...
}));

This is an object-returning Zustand selector: it returns a fresh object on every call, so logout gets a new reference on every store update. It is used in a useEffect dependency array at src/main.tsx:33 ([restoreSession, logout]), so the effect re-runs on every store update → store update → infinite effect/render loop → React error #185 (Maximum update depth exceeded).

Reproduction (from kanban QA card t_3b5379e0, zsus)

  1. Deploy docker_backup-app built from HEAD 7dd94b8 (served bundle index-CJpvs0MX.js, md5 fb5d3695482d02d656ca54010a64a3b8).
  2. Open http://127.0.0.1:3000 in a browser.
  3. Observe: blank page, console shows React error #185, no login form renders. Reproduced both unauthenticated and with a valid token.

Actual vs expected

  • Expected: SPA boots to the login page; after login, Settings renders the encryption-at-rest controls (source Settings.tsx:324-351 renders them, and the settings API round-trips encryptionEnabled/encryptionKey).
  • Actual: blank root, no UI at all.

Impact

This blocked acceptance criterion "UI shows encryption controls" for the encryption-at-rest E2E verification. The backup→.enc AES-GCM ciphertext and restore→byte-identical-payload criteria were verified at the API level; the UI criterion cannot be demonstrated until this is fixed. Screenshot of the blank /login page is attached to kanban card t_3b5379e0.

Fix direction

Use a primitive selector so the reference is stable: useAuthStore(s => s.logout) or wrap the object selector in useShallow. Then rebuild the frontend image and redeploy on zsus.

References

  • src/main.tsx:14-15, src/main.tsx:33
  • Kanban card: t_3b5379e0 (QA board, fixit)
## Summary The deployed SPA crashes on boot before rendering anything — the root element stays empty and no login form ever appears. This blocks the entire UI, including the Settings page's encryption-at-rest controls ("Encrypt backups at rest" Switch + Encryption Key input). ## Root cause `src/main.tsx:14-15`: ```ts const { logout } = useAuthStore(state => ({ logout: state.logout, // ... })); ``` This is an **object-returning Zustand selector**: it returns a fresh object on every call, so `logout` gets a new reference on every store update. It is used in a `useEffect` dependency array at `src/main.tsx:33` (`[restoreSession, logout]`), so the effect re-runs on every store update → store update → infinite effect/render loop → **React error #185 (Maximum update depth exceeded)**. ## Reproduction (from kanban QA card t_3b5379e0, zsus) 1. Deploy `docker_backup-app` built from HEAD `7dd94b8` (served bundle `index-CJpvs0MX.js`, md5 `fb5d3695482d02d656ca54010a64a3b8`). 2. Open `http://127.0.0.1:3000` in a browser. 3. Observe: blank page, console shows React error #185, no login form renders. Reproduced both unauthenticated and with a valid token. ## Actual vs expected - **Expected**: SPA boots to the login page; after login, Settings renders the encryption-at-rest controls (source `Settings.tsx:324-351` renders them, and the settings API round-trips `encryptionEnabled`/`encryptionKey`). - **Actual**: blank root, no UI at all. ## Impact This blocked acceptance criterion "UI shows encryption controls" for the encryption-at-rest E2E verification. The backup→`.enc` AES-GCM ciphertext and restore→byte-identical-payload criteria were verified at the API level; the UI criterion cannot be demonstrated until this is fixed. Screenshot of the blank `/login` page is attached to kanban card t_3b5379e0. ## Fix direction Use a primitive selector so the reference is stable: `useAuthStore(s => s.logout)` or wrap the object selector in `useShallow`. Then rebuild the frontend image and redeploy on zsus. ## References - `src/main.tsx:14-15`, `src/main.tsx:33` - Kanban card: t_3b5379e0 (QA board, fixit)
Mark 2026-08-14 11:13:43 +00:00
  • closed this issue
  • added the
    bug
    label
Sign in to join this conversation.
No description provided.