Skip to content

fix(windows): wrap pinnedCards Zustand selector in useShallow#139

Open
Aryansharma28 wants to merge 1 commit into
mainfrom
fix-pinnedcards-useshallow
Open

fix(windows): wrap pinnedCards Zustand selector in useShallow#139
Aryansharma28 wants to merge 1 commit into
mainfrom
fix-pinnedcards-useshallow

Conversation

@Aryansharma28

Copy link
Copy Markdown
Contributor

Symptom

App starts but the window is blank. DevTools shows:

```
Warning: The result of getSnapshot should be cached to avoid an infinite loop
at BoardView (BoardView.tsx:43:119)
→ Error: Maximum update depth exceeded.
```

React unmounts the entire tree → nothing renders.

Cause

```ts
// BoardView.tsx:24 — before
const pinnedCards = useBoardStore((s) => s.pinnedCards());
```

`pinnedCards()` is a computed helper that returns a freshly `.filter().sort()`-ed array on every call. Zustand v5 wraps React's `useSyncExternalStore`, which treats a new array reference as a state change → re-render → selector runs again → new array → infinite loop.

Latent since the Zustand v5 bump (`^5.0.4`). Surfaced consistently in current main; probably required a particular pinned-card state shape to trigger reliably before.

Fix

```ts
// after
import { useShallow } from "zustand/react/shallow";
const pinnedCards = useBoardStore(useShallow((s) => s.pinnedCards()));
```

`useShallow` does a shallow-equality compare on the returned array, so a new reference with the same card elements doesn't trigger a re-render. Canonical Zustand v5 pattern for selectors returning derived collections.

Test plan

  • `npm run build` — green
  • Window now renders the board instead of blank — confirmed via WebView2 + browser repro
  • Manual: open card with pinned items, verify Pinned section renders without crash

🤖 Generated with Claude Code

Symptom: blank window on app start — board renders nothing, devtools
shows "Maximum update depth exceeded" originating from BoardView, with
the preceding warning "The result of getSnapshot should be cached to
avoid an infinite loop".

Cause: `useBoardStore((s) => s.pinnedCards())` calls the computed helper
on every selector run, which returns a *new* filtered+sorted array each
time. Zustand v5 wraps React's useSyncExternalStore and treats a new
array reference as a state change → React re-renders → selector runs
again → new array → infinite loop → React unmounts the whole tree.

Latent bug — has been broken since Zustand bumped to ^5.0.4. Likely
masked until a particular state shape exposed the loop reliably.

Fix: `useBoardStore(useShallow((s) => s.pinnedCards()))`. useShallow
does a shallow array comparison, so a new array reference doesn't
trigger a re-render when its contents (card refs) are unchanged. This
is the canonical Zustand v5 pattern for selectors returning derived
collections.

Verified:
- `npm run build` green
- App window now renders the board instead of staying blank

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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