Live video on the AT Protocol

profile cache

+60 -14
+39
js/components/src/context/profile-cache.tsx
··· 1 + import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs"; 2 + import { createContext, useContext, useRef, useState } from "react"; 3 + 4 + interface ProfileCacheContextValue { 5 + profiles: Record<string, ProfileViewDetailed>; 6 + inFlight: React.MutableRefObject<Set<string>>; 7 + addProfiles: (profiles: Record<string, ProfileViewDetailed>) => void; 8 + } 9 + 10 + export const ProfileCacheContext = 11 + createContext<ProfileCacheContextValue | null>(null); 12 + 13 + export function ProfileCacheProvider({ 14 + children, 15 + }: { 16 + children: React.ReactNode; 17 + }) { 18 + const [profiles, setProfiles] = useState<Record<string, ProfileViewDetailed>>( 19 + {}, 20 + ); 21 + const inFlight = useRef<Set<string>>(new Set()); 22 + 23 + const addProfiles = (newProfiles: Record<string, ProfileViewDetailed>) => 24 + setProfiles((prev) => ({ ...prev, ...newProfiles })); 25 + 26 + return ( 27 + <ProfileCacheContext.Provider value={{ profiles, inFlight, addProfiles }}> 28 + {children} 29 + </ProfileCacheContext.Provider> 30 + ); 31 + } 32 + 33 + export function useProfileCache(): ProfileCacheContextValue { 34 + const ctx = useContext(ProfileCacheContext); 35 + if (!ctx) { 36 + throw new Error("useProfileCache must be used within ProfileCacheProvider"); 37 + } 38 + return ctx; 39 + }
+13 -9
js/components/src/hooks/useAvatars.tsx
··· 1 1 import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs"; 2 - import { useEffect, useMemo, useRef, useState } from "react"; 2 + import { useEffect, useMemo } from "react"; 3 + import { useProfileCache } from "../context/profile-cache"; 3 4 import { usePDSAgent } from "../streamplace-store/xrpc"; 4 5 5 6 export function useAvatars( 6 7 dids: string[], 7 8 ): Record<string, ProfileViewDetailed> { 8 - let agent = usePDSAgent(); 9 - const [profiles, setProfiles] = useState<Record<string, ProfileViewDetailed>>( 10 - {}, 11 - ); 12 - const inFlight = useRef<Set<string>>(new Set()); 9 + const agent = usePDSAgent(); 10 + const { profiles, inFlight, addProfiles } = useProfileCache(); 13 11 14 12 const missingDids = useMemo( 15 13 () => 16 14 dids.filter((did) => !(did in profiles) && !inFlight.current.has(did)), 17 - [dids, profiles], 15 + [dids, profiles, inFlight], 18 16 ); 19 17 20 18 useEffect(() => { ··· 29 27 result.data.profiles.forEach((p) => { 30 28 newProfiles[p.did] = p; 31 29 }); 32 - setProfiles((prev) => ({ ...prev, ...newProfiles })); 30 + addProfiles(newProfiles); 33 31 } catch (e) { 34 32 console.error("Failed to fetch profiles", e); 35 33 } finally { ··· 40 38 fetchProfiles(); 41 39 }, [missingDids, agent]); 42 40 43 - return profiles; 41 + return useMemo( 42 + () => 43 + Object.fromEntries( 44 + dids.filter((d) => d in profiles).map((d) => [d, profiles[d]]), 45 + ), 46 + [dids, profiles], 47 + ); 44 48 }
+8 -5
js/components/src/streamplace-provider/index.tsx
··· 1 1 import { SessionManager } from "@atproto/api/dist/session-manager"; 2 2 import { useEffect, useRef } from "react"; 3 + import { ProfileCacheProvider } from "../context/profile-cache"; 3 4 import { useDocumentTitle } from "../hooks"; 4 5 import { 5 6 useBrandingAutoFetch, ··· 32 33 33 34 return ( 34 35 <StreamplaceContext.Provider value={{ store: store }}> 35 - <BrandingFetcher> 36 - <ChatProfileCreator oauthSession={oauthSession}> 37 - <Poller>{children}</Poller> 38 - </ChatProfileCreator> 39 - </BrandingFetcher> 36 + <ProfileCacheProvider> 37 + <BrandingFetcher> 38 + <ChatProfileCreator oauthSession={oauthSession}> 39 + <Poller>{children}</Poller> 40 + </ChatProfileCreator> 41 + </BrandingFetcher> 42 + </ProfileCacheProvider> 40 43 </StreamplaceContext.Provider> 41 44 ); 42 45 }