mono/packages/ui/src/contexts/ProfilesContext.tsx
2026-02-02 00:12:16 +01:00

82 lines
3.3 KiB
TypeScript

import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
import { UserProfile } from "@/pages/Post/types";
interface ProfilesContextType {
profiles: Record<string, UserProfile>;
fetchProfile: (userId: string) => Promise<UserProfile | null>;
fetchProfiles: (userIds: string[]) => Promise<void>;
isLoading: boolean;
}
const ProfilesContext = createContext<ProfilesContextType | undefined>(undefined);
export const ProfilesProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [profiles, setProfiles] = useState<Record<string, UserProfile>>({});
const [isLoading, setIsLoading] = useState(false);
const fetchedIds = React.useRef<Set<string>>(new Set());
const profilesRef = React.useRef(profiles);
// Keep ref in sync
useEffect(() => {
profilesRef.current = profiles;
}, [profiles]);
const fetchProfiles = useCallback(async (userIds: string[]) => {
// Filter out existing AND already requested to prevent loops
const missingIds = userIds.filter(id => !fetchedIds.current.has(id) && !profilesRef.current[id]);
const uniqueMissingIds = Array.from(new Set(missingIds));
if (uniqueMissingIds.length === 0) return;
// Mark as fetched immediately to prevent re-entry
uniqueMissingIds.forEach(id => fetchedIds.current.add(id));
setIsLoading(true);
try {
// Use the new batch API endpoint
const SERVER_URL = import.meta.env.VITE_SERVER_IMAGE_API_URL;
const res = await fetch(`${SERVER_URL}/api/profiles?ids=${uniqueMissingIds.join(',')}`);
if (!res.ok) {
console.error('Error fetching profiles context:', res.statusText);
return;
}
const data: Record<string, UserProfile> = await res.json();
if (data) {
setProfiles(prev => {
const next = { ...prev, ...data };
// If any IDs are missing from response, we might want to mark them as 'not found'
// to prevent refetch loops, but for now we trust the server response map.
return next;
});
}
} catch (e) {
console.error('Error in fetchProfiles context:', e);
} finally {
setIsLoading(false);
}
}, []); // Stable callback
// but ideally remove it if logic allows.
// Since we check !profiles[id], we depend on it.
// However, fetchedIds prevents the loop even if profiles is stale.
const fetchProfile = useCallback(async (userId: string) => {
if (profiles[userId]) return profiles[userId];
await fetchProfiles([userId]);
return profiles[userId] || null;
}, [profiles, fetchProfiles]);
return (
<ProfilesContext.Provider value={{ profiles, fetchProfile, fetchProfiles, isLoading }}>
{children}
</ProfilesContext.Provider>
);
};
export const useProfiles = () => {
const context = useContext(ProfilesContext);
if (context === undefined) {
throw new Error('useProfiles must be used within a ProfilesProvider');
}
return context;
};