mirror of
https://github.com/thegeneralist01/archivr
synced 2026-07-22 03:05:32 +02:00
fix(frontend): guard j/k uncached-child fetch with monotonic token
Rapid j/k over child rows that aren't in entryCacheRef triggers fetchEntryDetail calls in parallel. Without a guard the last one to settle wins, desyncing selectedUids (highlight) from selectedEntry (detail panel/URL). Fix: - jkSeqRef (monotonic counter) incremented before each server fetch; the .then() guard tok === jkSeqRef.current drops results from superseded navigations - handleRowClick increments jkSeqRef.current so any click also cancels an in-flight j/k fetch
This commit is contained in:
parent
dea8b27fba
commit
bd90a4b77f
3 changed files with 23 additions and 17 deletions
|
|
@ -132,6 +132,8 @@ export default function App() {
|
|||
const pendingSearchFocus = useRef(false)
|
||||
const firstArchiveLoad = useRef(true)
|
||||
const lastAnchorIndexRef = useRef(null)
|
||||
// Monotonic token for j/k keyboard navigation; cancels stale fetchEntryDetail calls.
|
||||
const jkSeqRef = useRef(0)
|
||||
// uid → entry object cache; populated on every row click so ctrl/shift
|
||||
// selections can resolve child entries that aren't in the root entries array.
|
||||
const entryCacheRef = useRef(new Map())
|
||||
|
|
@ -267,6 +269,8 @@ export default function App() {
|
|||
// Cache every clicked entry so shift/ctrl can resolve child entries
|
||||
// that are not present in the root `entries` array.
|
||||
entryCacheRef.current.set(entry.entry_uid, entry)
|
||||
// Invalidate any in-flight j/k uncached-child fetch.
|
||||
++jkSeqRef.current
|
||||
|
||||
if (e.shiftKey && lastAnchorIndexRef.current !== null) {
|
||||
e.preventDefault()
|
||||
|
|
@ -479,9 +483,11 @@ export default function App() {
|
|||
const nextUid = nextNode.dataset.entryUid
|
||||
|
||||
lastAnchorIndexRef.current = nextUid
|
||||
const tok = ++jkSeqRef.current
|
||||
setSelectedUids(new Set([nextUid]))
|
||||
|
||||
// Resolve entry object: cache → root entries array → server fetch
|
||||
// Resolve entry object: cache → root entries array → server fetch.
|
||||
// Token guards against a slow fetch settling after the user has moved on.
|
||||
const cached = entryCacheRef.current.get(nextUid)
|
||||
?? entries.find(x => x.entry_uid === nextUid)
|
||||
?? null
|
||||
|
|
@ -489,7 +495,7 @@ export default function App() {
|
|||
selectEntry(cached)
|
||||
} else {
|
||||
fetchEntryDetail(archiveId, nextUid)
|
||||
.then(det => { if (det?.summary) selectEntry(det.summary) })
|
||||
.then(det => { if (tok === jkSeqRef.current && det?.summary) selectEntry(det.summary) })
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue