feat: add useDecisions hook with optimistic updates

This commit is contained in:
Viktor Barzin 2026-02-21 13:51:50 +00:00
parent 813f048e46
commit 2fdeebbae1
No known key found for this signature in database
GPG key ID: 0EB088298288D958

View file

@ -0,0 +1,100 @@
import { useState, useEffect, useCallback, useMemo } from 'react';
import type { AuthUser } from '@/auth/types';
import type { DecisionType } from '@/types';
import { fetchDecisions, setDecision as apiSetDecision, clearDecision as apiClearDecision } from '@/services';
function decisionKey(listingId: number, listingType: string): string {
return `${listingId}-${listingType}`;
}
export function useDecisions(user: AuthUser | null) {
const [decisions, setDecisions] = useState<Map<string, DecisionType>>(new Map());
const [isLoaded, setIsLoaded] = useState(false);
// Load decisions on mount
useEffect(() => {
if (!user) return;
fetchDecisions(user)
.then((list) => {
const map = new Map<string, DecisionType>();
for (const d of list) {
map.set(decisionKey(d.listing_id, d.listing_type), d.decision);
}
setDecisions(map);
setIsLoaded(true);
})
.catch(() => setIsLoaded(true));
}, [user]);
const decide = useCallback(
async (listingId: number, decision: DecisionType, listingType: 'RENT' | 'BUY' = 'RENT') => {
if (!user) return;
const key = decisionKey(listingId, listingType);
// Optimistic update
setDecisions((prev) => {
const next = new Map(prev);
next.set(key, decision);
return next;
});
try {
await apiSetDecision(user, listingId, decision, listingType);
} catch {
// Revert on failure
setDecisions((prev) => {
const next = new Map(prev);
next.delete(key);
return next;
});
}
},
[user],
);
const clear = useCallback(
async (listingId: number, listingType: 'RENT' | 'BUY' = 'RENT') => {
if (!user) return;
const key = decisionKey(listingId, listingType);
const previous = decisions.get(key);
setDecisions((prev) => {
const next = new Map(prev);
next.delete(key);
return next;
});
try {
await apiClearDecision(user, listingId, listingType);
} catch {
if (previous) {
setDecisions((prev) => {
const next = new Map(prev);
next.set(key, previous);
return next;
});
}
}
},
[user, decisions],
);
const getDecision = useCallback(
(listingId: number, listingType: string = 'RENT'): DecisionType | undefined => {
return decisions.get(decisionKey(listingId, listingType));
},
[decisions],
);
const likedCount = useMemo(
() => Array.from(decisions.values()).filter((d) => d === 'liked').length,
[decisions],
);
const dislikedCount = useMemo(
() => Array.from(decisions.values()).filter((d) => d === 'disliked').length,
[decisions],
);
return { decisions, isLoaded, decide, clear, getDecision, likedCount, dislikedCount };
}