fix(spa): skeleton only the table on a list filter change (#508)#509
Conversation
Changing a list_filter (or page / search / ordering) replaced the whole changelist with a full-page skeleton until the new rows arrived — the title, search box, filter chips and Customize button all vanished, making each filter interaction feel like a hard reload. Django's changelist only re-renders the results table. useList is backed by useSwrCache, whose cache key encodes the filters. A filter change → new key → the hook reset data to the new key's cache (null for an unseen combo), and ListPage shows its full-page skeleton whenever `loading && !data`, so the page blanked. That key-reset is right for a detail view (object→object must not flash the previous record, #416) but wrong for a list, where the chrome and columns are filter-independent. Add a `keepPreviousData` option to useSwrCache: on a key change it keeps the previous value on screen and shows a foreground load instead of blanking (adopting the new key's cache immediately when it exists). useList opts in; useDetail keeps the reset. ListPage is remounted per model so the retained-data behaviour is scoped to same-model filter/page changes and a model switch still resets cleanly. Result: the header stays put and only the Table shows skeleton rows (its built-in loading state) on a filter change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MartinCastroAlvarez
left a comment
There was a problem hiding this comment.
Review — Reviewer lane (independent; did not author). Clear to merge.
Design: correct on both axes. keepPreviousData: true keeps the prior rows + page chrome while a within-model filter/page/search change loads (table-only skeleton), and KeyedListPage keys ListPage on app/model so a model switch remounts — stale rows from the previous model can never flash, and per-model selection state resets cleanly.
Regression safety: the #416 detail behavior is preserved — detail doesn't opt into keepPreviousData, so a new uncached key still blanks (no flash of the wrong record). The third test (adopts the new key cached value immediately) confirms a cached key shows at once rather than holding stale data.
Security: none. keepPreviousData only retains the same user's, same-model, permission-filtered rows across a filter/page change; model changes remount; Cache-Control: no-store is untouched.
Tests: 3 targeted cases in swr-cache.test.ts covering default-blank (#416), keep-previous (#368), and immediate-cache-adoption. Good coverage of the data-layer behavior that drives the skeleton.
Verification basis: code + test inspection; CodeQL green. UX not browser-verified and the frontend suite isn't server-enforced yet (pending #506), but the change is unit-covered and low-risk. Merging per the ship-ASAP authorization.
Summary
Closes #508. Changing a
list_filter(or page / search / ordering) blanked the whole changelist to a full-page skeleton until the new rows arrived — the title, search box, filter chips and Customize button all vanished. Now the chrome stays put and only the table shows a loading skeleton, matching Django's changelist.Root cause
useList→useSwrCache, whose cache key encodes the filters. A filter change → new key → the hook resetdatato the new key's cache (nullfor an unseen combo) →ListPage'sloading && !datafull-page skeleton fired. That reset is correct for a detail view (object→object must not flash the previous record, #416) but wrong for a list.Fix
keepPreviousDataoption touseSwrCache: on a key change, keep the previous value on screen and show a foreground load instead of blanking — still adopting the new key's cached value immediately when present.useListopts in;useDetailkeeps the reset (so Detail→detail navigation shows stale content instead of a skeleton during load #416 stays fixed).ListPageper model (<KeyedListPage>) so retained-data is scoped to same-model filter/page/search changes; a model switch resets cleanly (no stale columns or carried-over selection).Tableprimitive already renders skeleton rows under the real headers whenloading.No backend / contract / dep changes.
Role
Author. Eligible for same-session review/merge once CI is green per repo multi-lane authorization.
Test plan
swr-cache.test.ts: default blanks on key change (Detail→detail navigation shows stale content instead of a skeleton during load #416);keepPreviousDataretains previous data + foreground load; adopts the new key's cache immediately when present. (8 passed)@dar/datavitest suite green (31 passed).@dar/web+@dar/datatypecheck clean;eslint --max-warnings 0clean.🤖 Generated with Claude Code