fix: batch favorite status checks
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user