···8282 // This should not happen because of waitForInitialization={true}.
8383 console.error('Did not expected isLoading to ever be true.')
8484 }
8585- return value
8585+ // This shouldn't technically be necessary but let's get a strong
8686+ // guarantee that a gate value can never change while mounted.
8787+ const [initialValue] = React.useState(value)
8888+ return initialValue
8689}
87908891function toStatsigUser(did: string | undefined) {
+61-4
src/view/screens/Search/Search.tsx
···3232import {useActorSearch} from '#/state/queries/actor-search'
3333import {useModerationOpts} from '#/state/queries/preferences'
3434import {useSearchPostsQuery} from '#/state/queries/search-posts'
3535-import {useGetSuggestedFollowersByActor} from '#/state/queries/suggested-follows'
3535+import {
3636+ useGetSuggestedFollowersByActor,
3737+ useSuggestedFollowsQuery,
3838+} from '#/state/queries/suggested-follows'
3639import {useSession} from '#/state/session'
3740import {useSetDrawerOpen} from '#/state/shell'
3841import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
···118121 )
119122}
120123121121-function SearchScreenSuggestedFollows() {
122122- const pal = usePalette('default')
124124+function useSuggestedFollowsV1(): [
125125+ AppBskyActorDefs.ProfileViewBasic[],
126126+ () => void,
127127+] {
123128 const {currentAccount} = useSession()
124129 const [suggestions, setSuggestions] = React.useState<
125130 AppBskyActorDefs.ProfileViewBasic[]
···162167 }
163168 }, [currentAccount, setSuggestions, getSuggestedFollowsByActor])
164169170170+ return [suggestions, () => {}]
171171+}
172172+173173+function useSuggestedFollowsV2(): [
174174+ AppBskyActorDefs.ProfileViewBasic[],
175175+ () => void,
176176+] {
177177+ const {
178178+ data: suggestions,
179179+ hasNextPage,
180180+ isFetchingNextPage,
181181+ isError,
182182+ fetchNextPage,
183183+ } = useSuggestedFollowsQuery()
184184+185185+ const onEndReached = React.useCallback(async () => {
186186+ if (isFetchingNextPage || !hasNextPage || isError) return
187187+ try {
188188+ await fetchNextPage()
189189+ } catch (err) {
190190+ logger.error('Failed to load more suggested follows', {message: err})
191191+ }
192192+ }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
193193+194194+ const items: AppBskyActorDefs.ProfileViewBasic[] = []
195195+ if (suggestions) {
196196+ // Currently the responses contain duplicate items.
197197+ // Needs to be fixed on backend, but let's dedupe to be safe.
198198+ let seen = new Set()
199199+ for (const page of suggestions.pages) {
200200+ for (const actor of page.actors) {
201201+ if (!seen.has(actor.did)) {
202202+ seen.add(actor.did)
203203+ items.push(actor)
204204+ }
205205+ }
206206+ }
207207+ }
208208+ return [items, onEndReached]
209209+}
210210+211211+function SearchScreenSuggestedFollows() {
212212+ const pal = usePalette('default')
213213+ const useSuggestedFollows = useGate('use_new_suggestions_endpoint')
214214+ ? // Conditional hook call here is *only* OK because useGate()
215215+ // result won't change until a remount.
216216+ useSuggestedFollowsV2
217217+ : useSuggestedFollowsV1
218218+ const [suggestions, onEndReached] = useSuggestedFollows()
219219+165220 return suggestions.length ? (
166221 <List
167222 data={suggestions}
···169224 keyExtractor={item => item.did}
170225 // @ts-ignore web only -prf
171226 desktopFixedHeight
172172- contentContainerStyle={{paddingBottom: 1200}}
227227+ contentContainerStyle={{paddingBottom: 200}}
173228 keyboardShouldPersistTaps="handled"
174229 keyboardDismissMode="on-drag"
230230+ onEndReached={onEndReached}
231231+ onEndReachedThreshold={2}
175232 />
176233 ) : (
177234 <CenteredView sideBorders style={[pal.border, s.hContentRegion]}>