82 lines
3.3 KiB
TypeScript
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;
|
|
};
|