Mirror — see github.com/blacksky-algorithms/blacksky.community
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge Bluesky counts into profile query to fix follower count flickering

Profile headers were making a separate useBskyProfileQuery call that
raced with the main useProfileQuery, causing visible flickering between
our appview's counts and Bluesky's. Merge the Bluesky fetch directly
into useProfileQuery via Promise.all so counts and known followers
arrive in a single render with no race condition.

+59 -16
+4 -7
src/screens/Profile/Header/Metrics.tsx
··· 5 5 6 6 import {makeProfileLink} from '#/lib/routes/links' 7 7 import {type Shadow} from '#/state/cache/types' 8 - import {useBskyProfileQuery} from '#/state/queries/profile' 9 8 import {formatCount} from '#/view/com/util/numeric/format' 10 9 import {atoms as a, useTheme} from '#/alf' 11 10 import {InlineLinkText} from '#/components/Link' ··· 18 17 }) { 19 18 const t = useTheme() 20 19 const {_, i18n} = useLingui() 21 - const {data: bskyProfile} = useBskyProfileQuery({did: profile.did}) 22 20 23 - // Prefer Bluesky counts, fall back to local when user is suspended on Bluesky 24 - const followsCount = bskyProfile?.followsCount ?? profile.followsCount ?? 0 25 - const followersCount = 26 - bskyProfile?.followersCount ?? profile.followersCount ?? 0 27 - const postsCount = bskyProfile?.postsCount ?? profile.postsCount ?? 0 21 + // Counts are merged from Bluesky's API in useProfileQuery 22 + const followsCount = profile.followsCount ?? 0 23 + const followersCount = profile.followersCount ?? 0 24 + const postsCount = profile.postsCount ?? 0 28 25 29 26 const following = formatCount(i18n, followsCount) 30 27 const followers = formatCount(i18n, followersCount)
+2 -5
src/screens/Profile/Header/ProfileHeaderStandard.tsx
··· 17 17 import {logger} from '#/logger' 18 18 import {type Shadow, useProfileShadow} from '#/state/cache/profile-shadow' 19 19 import { 20 - useBskyProfileQuery, 21 20 useProfileBlockMutationQueue, 22 21 useProfileFollowMutationQueue, 23 22 } from '#/state/queries/profile' ··· 93 92 } 94 93 95 94 const isMe = currentAccount?.did === profile.did 96 - const {data: bskyProfile} = useBskyProfileQuery({did: profile.did}) 97 95 98 - // Prefer Bluesky known followers, fall back to local 99 - const knownFollowers = 100 - bskyProfile?.viewer?.knownFollowers ?? profile.viewer?.knownFollowers 96 + // Known followers are merged from Bluesky's API in useProfileQuery 97 + const knownFollowers = profile.viewer?.knownFollowers 101 98 102 99 const {isActive: live} = useActorStatus(profile) 103 100
+53 -4
src/state/queries/profile.ts
··· 82 82 refetchOnWindowFocus: true, 83 83 queryKey: RQKEY(did ?? ''), 84 84 queryFn: async () => { 85 - const res = await agent.getProfile({actor: did ?? ''}) 86 - return res.data 85 + const profilePromise = agent.getProfile({actor: did ?? ''}) 86 + const bskyPromise = agent.app.bsky.actor 87 + .getProfile({actor: did ?? ''}, {headers: BSKY_PROXY_HEADER}) 88 + .catch(() => null) 89 + 90 + const [profileRes, bskyRes] = await Promise.all([ 91 + profilePromise, 92 + bskyPromise, 93 + ]) 94 + const profile = profileRes.data 95 + 96 + // Prefer Bluesky's counts and known followers to avoid flickering 97 + // between our appview's counts and Bluesky's 98 + if (bskyRes?.data) { 99 + profile.followersCount = 100 + bskyRes.data.followersCount ?? profile.followersCount 101 + profile.followsCount = bskyRes.data.followsCount ?? profile.followsCount 102 + profile.postsCount = bskyRes.data.postsCount ?? profile.postsCount 103 + if (bskyRes.data.viewer?.knownFollowers) { 104 + profile.viewer = { 105 + ...profile.viewer, 106 + knownFollowers: bskyRes.data.viewer.knownFollowers, 107 + } 108 + } 109 + } 110 + 111 + return profile 87 112 }, 88 113 placeholderData: () => { 89 114 if (!did) return ··· 172 197 staleTime: STALE.SECONDS.THIRTY, 173 198 queryKey: RQKEY(did), 174 199 queryFn: async () => { 175 - const res = await agent.getProfile({actor: did || ''}) 176 - return res.data 200 + const profilePromise = agent.getProfile({actor: did || ''}) 201 + const bskyPromise = agent.app.bsky.actor 202 + .getProfile({actor: did || ''}, {headers: BSKY_PROXY_HEADER}) 203 + .catch(() => null) 204 + 205 + const [profileRes, bskyRes] = await Promise.all([ 206 + profilePromise, 207 + bskyPromise, 208 + ]) 209 + const profile = profileRes.data 210 + 211 + if (bskyRes?.data) { 212 + profile.followersCount = 213 + bskyRes.data.followersCount ?? profile.followersCount 214 + profile.followsCount = 215 + bskyRes.data.followsCount ?? profile.followsCount 216 + profile.postsCount = bskyRes.data.postsCount ?? profile.postsCount 217 + if (bskyRes.data.viewer?.knownFollowers) { 218 + profile.viewer = { 219 + ...profile.viewer, 220 + knownFollowers: bskyRes.data.viewer.knownFollowers, 221 + } 222 + } 223 + } 224 + 225 + return profile 177 226 }, 178 227 enableFallback: true, 179 228 fallbackType: 'profile',