fix: batch favorite status checks

This commit is contained in:
TerryM
2026-06-02 01:11:00 +08:00
parent fb6cb5bc11
commit b9fe7ff168

View File

@@ -36,19 +36,52 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
const [pendingIds, setPendingIds] = useState<Set<string>>(() => new Set()); const [pendingIds, setPendingIds] = useState<Set<string>>(() => new Set());
const pendingAfterLoginRef = useRef<string | null>(null); const pendingAfterLoginRef = useRef<string | null>(null);
const lastAddressRef = useRef<string | null>(null); const lastAddressRef = useRef<string | null>(null);
const knownIdsRef = useRef<Set<string>>(new Set());
const inFlightIdsRef = useRef<Set<string>>(new Set());
const queuedIdsRef = useRef<Set<string>>(new Set());
const batchTimerRef = useRef<number | null>(null);
const tokenRef = useRef<string | null>(null);
useEffect(() => {
tokenRef.current = status === "loggedIn" ? token : null;
}, [status, token]);
const clearFavoriteStatus = useCallback(() => {
knownIdsRef.current = new Set();
inFlightIdsRef.current = new Set();
queuedIdsRef.current = new Set();
if (batchTimerRef.current !== null) {
window.clearTimeout(batchTimerRef.current);
batchTimerRef.current = null;
}
setFavoriteIds(new Set());
setKnownIds(new Set());
setPendingIds(new Set());
}, []);
useEffect(() => { useEffect(() => {
const nextAddress = status === "loggedIn" ? address : null; const nextAddress = status === "loggedIn" ? address : null;
if (lastAddressRef.current === nextAddress) return; if (lastAddressRef.current === nextAddress) return;
lastAddressRef.current = nextAddress; lastAddressRef.current = nextAddress;
setFavoriteIds(new Set()); clearFavoriteStatus();
setKnownIds(new Set());
setPendingIds(new Set());
if (!nextAddress) pendingAfterLoginRef.current = null; if (!nextAddress) pendingAfterLoginRef.current = null;
}, [address, status]); }, [address, clearFavoriteStatus, status]);
useEffect(
() => () => {
if (batchTimerRef.current !== null) {
window.clearTimeout(batchTimerRef.current);
}
},
[],
);
const markFavorite = useCallback((resourceId: string, favorited: boolean) => { const markFavorite = useCallback((resourceId: string, favorited: boolean) => {
setKnownIds((prev) => new Set(prev).add(resourceId)); setKnownIds((prev) => {
const next = new Set(prev).add(resourceId);
knownIdsRef.current = next;
return next;
});
setFavoriteIds((prev) => { setFavoriteIds((prev) => {
const next = new Set(prev); const next = new Set(prev);
if (favorited) next.add(resourceId); if (favorited) next.add(resourceId);
@@ -57,17 +90,22 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
}); });
}, []); }, []);
const ensureFavoriteIds = useCallback( const flushFavoriteIdBatch = useCallback(async () => {
async (resourceIds: string[]) => { const requestToken = tokenRef.current;
if (!token || status !== "loggedIn") return; const requestIds = Array.from(queuedIdsRef.current);
const missing = [...new Set(resourceIds)].filter( queuedIdsRef.current.clear();
(id) => !knownIds.has(id), if (!requestToken || requestIds.length === 0) {
); requestIds.forEach((id) => inFlightIdsRef.current.delete(id));
if (missing.length === 0) return; return;
const ids = await getFavoriteIds(token, missing); }
try {
const ids = await getFavoriteIds(requestToken, requestIds);
if (tokenRef.current !== requestToken) return;
setKnownIds((prev) => { setKnownIds((prev) => {
const next = new Set(prev); const next = new Set(prev);
missing.forEach((id) => next.add(id)); requestIds.forEach((id) => next.add(id));
knownIdsRef.current = next;
return next; return next;
}); });
setFavoriteIds((prev) => { setFavoriteIds((prev) => {
@@ -75,8 +113,35 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
ids.forEach((id) => next.add(id)); ids.forEach((id) => next.add(id));
return next; return next;
}); });
} finally {
requestIds.forEach((id) => inFlightIdsRef.current.delete(id));
if (queuedIdsRef.current.size > 0 && batchTimerRef.current === null) {
batchTimerRef.current = window.setTimeout(() => {
batchTimerRef.current = null;
void flushFavoriteIdBatch();
}, 0);
}
}
}, []);
const ensureFavoriteIds = useCallback(
async (resourceIds: string[]) => {
if (!token || status !== "loggedIn") return;
const missing = [...new Set(resourceIds)].filter(
(id) => !knownIdsRef.current.has(id) && !inFlightIdsRef.current.has(id),
);
if (missing.length === 0) return;
missing.forEach((id) => {
queuedIdsRef.current.add(id);
inFlightIdsRef.current.add(id);
});
if (batchTimerRef.current !== null) return;
batchTimerRef.current = window.setTimeout(() => {
batchTimerRef.current = null;
void flushFavoriteIdBatch();
}, 0);
}, },
[knownIds, status, token], [flushFavoriteIdBatch, status, token],
); );
const runFavoriteMutation = useCallback( const runFavoriteMutation = useCallback(