Fix pagination and rendering of suggested follows (#95)

zio/stable
Paul Frazee 2023-01-25 17:46:14 -06:00 committed by GitHub
parent eb33c3fa81
commit 1090783f91
2 changed files with 68 additions and 46 deletions

View File

@ -2,9 +2,9 @@ import {makeAutoObservable} from 'mobx'
import {AppBskyActorGetSuggestions as GetSuggestions} from '@atproto/api' import {AppBskyActorGetSuggestions as GetSuggestions} from '@atproto/api'
import {RootStoreModel} from './root-store' import {RootStoreModel} from './root-store'
export type SuggestedActor = GetSuggestions.Actor & { const PAGE_SIZE = 30
_reactKey: string
} export type SuggestedActor = GetSuggestions.Actor
export class SuggestedActorsViewModel { export class SuggestedActorsViewModel {
// state // state
@ -12,6 +12,9 @@ export class SuggestedActorsViewModel {
isRefreshing = false isRefreshing = false
hasLoaded = false hasLoaded = false
error = '' error = ''
hasMore = true
loadMoreCursor?: string
private _loadMorePromise: Promise<void> | undefined
// data // data
suggestions: SuggestedActor[] = [] suggestions: SuggestedActor[] = []
@ -41,12 +44,17 @@ export class SuggestedActorsViewModel {
// public api // public api
// = // =
async setup() { async refresh() {
await this._fetch() return this.loadMore(true)
} }
async refresh() { async loadMore(isRefreshing = false) {
await this._fetch(true) if (this._loadMorePromise) {
return this._loadMorePromise
}
this._loadMorePromise = this._loadMore(isRefreshing)
await this._loadMorePromise
this._loadMorePromise = undefined
} }
// state transitions // state transitions
@ -71,46 +79,43 @@ export class SuggestedActorsViewModel {
// loader functions // loader functions
// = // =
private async _fetch(isRefreshing = false) { private async _loadMore(isRefreshing = false) {
this.suggestions.length = 0 if (!this.hasMore) {
return
}
this._xLoading(isRefreshing) this._xLoading(isRefreshing)
let cursor
let res
try { try {
if (this.isRefreshing) {
this.suggestions = []
}
let res
let totalAdded = 0
do { do {
res = await this.rootStore.api.app.bsky.actor.getSuggestions({ res = await this.rootStore.api.app.bsky.actor.getSuggestions({
limit: 20, limit: PAGE_SIZE,
cursor, cursor: this.loadMoreCursor,
}) })
this._appendAll(res) totalAdded += await this._appendAll(res)
cursor = res.data.cursor } while (totalAdded < PAGE_SIZE && this.hasMore)
} while (
cursor &&
res.data.actors.length === 20 &&
this.suggestions.length < 20
)
this._xIdle() this._xIdle()
} catch (e: any) { } catch (e: any) {
this._xIdle(e) this._xIdle(e)
} }
} }
private _appendAll(res: GetSuggestions.Response) { private async _appendAll(res: GetSuggestions.Response) {
for (const item of res.data.actors) { this.loadMoreCursor = res.data.cursor
if (item.did === this.rootStore.me.did) { this.hasMore = !!this.loadMoreCursor
continue // skip self const newSuggestions = res.data.actors.filter(actor => {
if (actor.did === this.rootStore.me.did) {
return false // skip self
} }
if (item.myState?.follow) { if (actor.myState?.follow) {
continue // skip already-followed users return false // skip already-followed users
} }
this._append({ return true
_reactKey: `item-${this.suggestions.length}`,
...item,
}) })
} this.suggestions = this.suggestions.concat(newSuggestions)
} return newSuggestions.length
private _append(item: SuggestedActor) {
this.suggestions.push(item)
} }
} }

View File

@ -44,7 +44,7 @@ export const SuggestedFollows = observer(
useEffect(() => { useEffect(() => {
view view
.setup() .loadMore()
.catch((err: any) => .catch((err: any) =>
store.log.error('Failed to fetch suggestions', err), store.log.error('Failed to fetch suggestions', err),
) )
@ -56,12 +56,20 @@ export const SuggestedFollows = observer(
} }
}, [view, view.isLoading, view.hasError, view.hasContent, onNoSuggestions]) }, [view, view.isLoading, view.hasError, view.hasContent, onNoSuggestions])
const onPressTryAgain = () => const onRefresh = () => {
view view
.setup() .refresh()
.catch((err: any) => .catch((err: any) =>
store.log.error('Failed to fetch suggestions', err), store.log.error('Failed to fetch suggestions', err),
) )
}
const onEndReached = () => {
view
.loadMore()
.catch(err =>
view?.rootStore.log.error('Failed to load more suggestions', err),
)
}
const onPressFollow = async (item: SuggestedActor) => { const onPressFollow = async (item: SuggestedActor) => {
try { try {
@ -108,16 +116,12 @@ export const SuggestedFollows = observer(
} }
return ( return (
<View style={styles.container}> <View style={styles.container}>
{view.isLoading ? ( {view.hasError ? (
<View>
<ActivityIndicator />
</View>
) : view.hasError ? (
<ErrorScreen <ErrorScreen
title="Failed to load suggestions" title="Failed to load suggestions"
message="There was an error while trying to load suggested follows." message="There was an error while trying to load suggested follows."
details={view.error} details={view.error}
onPressTryAgain={onPressTryAgain} onPressTryAgain={onRefresh}
/> />
) : view.isEmpty ? ( ) : view.isEmpty ? (
<View /> <View />
@ -125,10 +129,19 @@ export const SuggestedFollows = observer(
<View style={[styles.suggestionsContainer, pal.view]}> <View style={[styles.suggestionsContainer, pal.view]}>
<FlatList <FlatList
data={view.suggestions} data={view.suggestions}
keyExtractor={item => item._reactKey} keyExtractor={item => item.did}
refreshing={view.isRefreshing}
onRefresh={onRefresh}
onEndReached={onEndReached}
renderItem={renderItem} renderItem={renderItem}
style={s.flex1} initialNumToRender={15}
ListFooterComponent={() => (
<View style={styles.footer}>
{view.isLoading && <ActivityIndicator />}
</View>
)}
contentContainerStyle={s.contentContainer} contentContainerStyle={s.contentContainer}
style={s.flex1}
/> />
</View> </View>
)} )}
@ -214,6 +227,10 @@ const styles = StyleSheet.create({
suggestionsContainer: { suggestionsContainer: {
flex: 1, flex: 1,
}, },
footer: {
height: 200,
paddingTop: 20,
},
actor: { actor: {
borderTopWidth: 1, borderTopWidth: 1,