fix: resolve stale closure in updateNode causing unread indicator to not clear#374
Conversation
…not clear The updateNode function captured `data` from the closure, which could be undefined or stale when called from an async callback. When a user opened an unread page, the POST to mark as viewed would complete, but the updateNode call would operate on stale data (often an empty array), failing to find the node and never clearing the blue dot. Fix uses SWR's functional mutate pattern to always receive the current cache data as an argument, ensuring updates work correctly regardless of when the callback executes. https://claude.ai/code/session_01GGDSpoDcDwMq2aB4jmu5na
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
📝 WalkthroughWalkthroughUsePageTree now applies SWR functional updaters and memoized callbacks for optimistic edits; fetch-and-merge and node-updates use functional Changes
Sequence Diagram(s)sequenceDiagram
participant Page as Page Component
participant API as Page View API
participant Hook as usePageTree Hook
participant SWR as SWR Cache
Page->>API: POST /page/view
API-->>Page: 200 OK
Page->>Hook: check isTreeLoadingRef (mirrors isLoading)
alt tree was loading
Hook->>SWR: mutate(updater, {revalidate: true})
SWR-->>Hook: cache revalidated / updated
end
SWR-->>Page: UI reflects updated tree (via SWR subscription)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Update tests to match the new updateNode implementation that uses SWR's
functional mutate pattern. Tests now verify:
- mutate is called with a function and { revalidate: false }
- The updater function produces the correct tree when given current data
- The updater handles undefined data (loading state) gracefully
https://claude.ai/code/session_01GGDSpoDcDwMq2aB4jmu5na
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/src/hooks/__tests__/usePageTree.test.ts (1)
220-244:⚠️ Potential issue | 🔴 CriticalUse functional updater pattern for
fetchAndMergeChildrento fix stale closure issue.The concern is valid.
fetchAndMergeChildrencapturesdatafrom closure and then awaits an async operation before callingmutate(updatedTree, false). While awaiting the fetcher, WebSocket events fromusePageTreeSocketcan trigger concurrent mutations viainvalidateTree, making the captureddatastale. This mirrors the stale closure vulnerability that was already fixed inupdateNodeusing a functional updater.Change line 81-83 from:
const currentTree = data || []; const updatedTree = mergeChildren(currentTree, pageId, children); mutate(updatedTree, false);To use the functional updater pattern like
updateNode:mutate((currentData) => { const currentTree = currentData || []; return mergeChildren(currentTree, pageId, children); }, { revalidate: false });The test should also be updated to verify the functional behavior, not just that
mutatewas called withexpect.any(Array).
The updateNode function captured
datafrom the closure, which could beundefined or stale when called from an async callback. When a user opened
an unread page, the POST to mark as viewed would complete, but the
updateNode call would operate on stale data (often an empty array),
failing to find the node and never clearing the blue dot.
Fix uses SWR's functional mutate pattern to always receive the current
cache data as an argument, ensuring updates work correctly regardless
of when the callback executes.
https://claude.ai/code/session_01GGDSpoDcDwMq2aB4jmu5na
Summary by CodeRabbit
Refactor
Bug Fixes
Tests