terry-staging #16
41
.unipi/docs/fix/2026-06-04-favorites-display-loading-fix.md
Normal file
41
.unipi/docs/fix/2026-06-04-favorites-display-loading-fix.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: "Favorites Display Loading Blank Page — Quick Fix"
|
||||
type: quick-fix
|
||||
date: 2026-06-04
|
||||
---
|
||||
|
||||
# Favorites Display Loading Blank Page — Quick Fix
|
||||
|
||||
## Bug
|
||||
|
||||
When clicking the desktop header “我的收藏” button, the favorites page could briefly show the no-favorites empty state and then appear blank. The correct behavior is to show the user's favorited posts after loading.
|
||||
|
||||
## Root Cause
|
||||
|
||||
Two issues combined:
|
||||
|
||||
1. The favorites page initialized with `loading=false` and `items=[]`. When the wallet was already logged in, React rendered the empty state once before the `useEffect` started the favorites request.
|
||||
2. The desktop header favorites link had been changed to `reloadDocument` as a previous workaround. In the local Vite/dev-browser state this could force a full document reload and land in a broken empty document state instead of keeping the React app mounted.
|
||||
|
||||
## Fix
|
||||
|
||||
- Added an explicit `loaded` state to `src/pages/Favorites/index.tsx`.
|
||||
- The favorites page now shows loading skeletons while logged-in favorites have not completed their first load, so the empty state only appears after a completed request returns zero items.
|
||||
- Added a loading UI for `wallet.status === "loading"` so a persisted wallet token does not briefly show the logged-out prompt.
|
||||
- Removed `reloadDocument` from the desktop header favorites link and kept client-side navigation with a top scroll reset.
|
||||
|
||||
### Files Modified
|
||||
|
||||
- `src/pages/Favorites/index.tsx` — tracks loaded state and gates empty-state rendering until favorites data has loaded.
|
||||
- `src/layouts/PublicLayout.tsx` — removes hard document reload from the desktop header favorites link.
|
||||
|
||||
## Verification
|
||||
|
||||
- `npx tsc --noEmit`
|
||||
- `npm run format:check`
|
||||
- `npm test`
|
||||
- Browser native: opened `http://192.168.1.187:5173/cn/browse`, clicked the desktop header “我的收藏”, and verified the resulting page URL is `/cn/favorites`, `document.getElementById("root")` exists, and `window.scrollY === 0`.
|
||||
|
||||
## Notes
|
||||
|
||||
No deploy was performed.
|
||||
@@ -698,7 +698,6 @@ export function PublicLayout() {
|
||||
/>
|
||||
<Link
|
||||
to={lp("/favorites")}
|
||||
reloadDocument
|
||||
onClick={() => window.scrollTo({ top: 0, left: 0 })}
|
||||
className={`hidden h-10 shrink-0 items-center justify-center gap-2 rounded-full border px-3 text-sm font-bold outline-none transition focus-visible:ring-2 focus-visible:ring-ark-gold/80 focus-visible:ring-offset-2 focus-visible:ring-offset-ark-bg md:inline-flex ${
|
||||
na("favorites")
|
||||
|
||||
@@ -137,6 +137,7 @@ export default function Favorites() {
|
||||
const [items, setItems] = useState<Resource[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [reloadKey, setReloadKey] = useState(0);
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
@@ -151,10 +152,13 @@ export default function Favorites() {
|
||||
if (!wallet.token || wallet.status !== "loggedIn") {
|
||||
setItems([]);
|
||||
setTotal(0);
|
||||
setLoading(false);
|
||||
setLoaded(false);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
setLoading(true);
|
||||
setLoaded(false);
|
||||
setError("");
|
||||
listFavorites(wallet.token, {
|
||||
sort,
|
||||
@@ -170,6 +174,7 @@ export default function Favorites() {
|
||||
setItems(resources);
|
||||
setTotal(data.total ?? resources.length);
|
||||
resources.forEach((resource) => markFavorite(resource.id, true));
|
||||
setLoaded(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (cancelled) return;
|
||||
@@ -179,6 +184,7 @@ export default function Favorites() {
|
||||
return;
|
||||
}
|
||||
setError(err instanceof Error ? err.message : t("loadFailed"));
|
||||
setLoaded(true);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
@@ -200,6 +206,16 @@ export default function Favorites() {
|
||||
[t],
|
||||
);
|
||||
|
||||
if (wallet.status === "loading") {
|
||||
return (
|
||||
<Reveal className="mx-auto grid max-w-[980px] gap-3 px-0 py-2 md:py-4">
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<Skeleton key={index} className="h-[132px] rounded-2xl" />
|
||||
))}
|
||||
</Reveal>
|
||||
);
|
||||
}
|
||||
|
||||
if (wallet.status !== "loggedIn") {
|
||||
return (
|
||||
<Reveal className="flex min-h-[60vh] flex-col items-center justify-center gap-5 px-4 py-12 text-center">
|
||||
@@ -330,7 +346,7 @@ export default function Favorites() {
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
{loading || !loaded ? (
|
||||
<div className="grid gap-3">
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<Skeleton key={index} className="h-[132px] rounded-2xl" />
|
||||
|
||||
Reference in New Issue
Block a user