Skip to content

Commit

Permalink
Refactor feeds to use react-query (#1862)
Browse files Browse the repository at this point in the history
* Update to react-query v5

* Introduce post-feed react query

* Add feed refresh behaviors

* Only fetch feeds of visible pages

* Implement polling for latest on feeds

* Add moderation filtering to slices

* Handle block errors

* Update feed error messages

* Remove old models

* Replace simple-feed option with disable-tuner option

* Add missing useMemo

* Implement the mergefeed and fixes to polling

* Correctly handle failed load more state

* Improve error and empty state behaviors

* Clearer naming
  • Loading branch information
pfrazee authored Nov 10, 2023
1 parent 51f04b9 commit c8c308e
Show file tree
Hide file tree
Showing 31 changed files with 904 additions and 1,081 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"@segment/analytics-react-native": "^2.10.1",
"@segment/sovran-react-native": "^0.4.5",
"@sentry/react-native": "5.10.0",
"@tanstack/react-query": "^4.33.0",
"@tanstack/react-query": "^5.8.1",
"@tiptap/core": "^2.0.0-beta.220",
"@tiptap/extension-document": "^2.0.0-beta.220",
"@tiptap/extension-hard-break": "^2.0.3",
Expand Down
17 changes: 14 additions & 3 deletions src/lib/api/feed-manip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
AppBskyEmbedRecordWithMedia,
AppBskyEmbedRecord,
} from '@atproto/api'
import {FeedSourceInfo} from './feed/types'
import {ReasonFeedSource} from './feed/types'
import {isPostInLanguage} from '../../locale/helpers'
type FeedViewPost = AppBskyFeedDefs.FeedViewPost

Expand Down Expand Up @@ -65,9 +65,9 @@ export class FeedViewPostsSlice {
)
}

get source(): FeedSourceInfo | undefined {
get source(): ReasonFeedSource | undefined {
return this.items.find(item => '__source' in item && !!item.__source)
?.__source as FeedSourceInfo
?.__source as ReasonFeedSource
}

containsUri(uri: string) {
Expand Down Expand Up @@ -116,6 +116,17 @@ export class FeedViewPostsSlice {
}
}

export class NoopFeedTuner {
reset() {}
tune(
feed: FeedViewPost[],
_tunerFns: FeedTunerFn[] = [],
_opts?: {dryRun: boolean; maintainOrder: boolean},
): FeedViewPostsSlice[] {
return feed.map(item => new FeedViewPostsSlice([item]))
}
}

export class FeedTuner {
seenUris: Set<string> = new Set()

Expand Down
25 changes: 12 additions & 13 deletions src/lib/api/feed/author.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
import {
AppBskyFeedDefs,
AppBskyFeedGetAuthorFeed as GetAuthorFeed,
BskyAgent,
} from '@atproto/api'
import {RootStoreModel} from 'state/index'
import {FeedAPI, FeedAPIResponse} from './types'

export class AuthorFeedAPI implements FeedAPI {
cursor: string | undefined

constructor(
public rootStore: RootStoreModel,
public agent: BskyAgent,
public params: GetAuthorFeed.QueryParams,
) {}

reset() {
this.cursor = undefined
}

async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.rootStore.agent.getAuthorFeed({
const res = await this.agent.getAuthorFeed({
...this.params,
limit: 1,
})
return res.data.feed[0]
}

async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
const res = await this.rootStore.agent.getAuthorFeed({
async fetch({
cursor,
limit,
}: {
cursor: string | undefined
limit: number
}): Promise<FeedAPIResponse> {
const res = await this.agent.getAuthorFeed({
...this.params,
cursor: this.cursor,
cursor,
limit,
})
if (res.success) {
this.cursor = res.data.cursor
return {
cursor: res.data.cursor,
feed: this._filter(res.data.feed),
Expand Down
25 changes: 12 additions & 13 deletions src/lib/api/feed/custom.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
import {
AppBskyFeedDefs,
AppBskyFeedGetFeed as GetCustomFeed,
BskyAgent,
} from '@atproto/api'
import {RootStoreModel} from 'state/index'
import {FeedAPI, FeedAPIResponse} from './types'

export class CustomFeedAPI implements FeedAPI {
cursor: string | undefined

constructor(
public rootStore: RootStoreModel,
public agent: BskyAgent,
public params: GetCustomFeed.QueryParams,
) {}

reset() {
this.cursor = undefined
}

async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.rootStore.agent.app.bsky.feed.getFeed({
const res = await this.agent.app.bsky.feed.getFeed({
...this.params,
limit: 1,
})
return res.data.feed[0]
}

async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
const res = await this.rootStore.agent.app.bsky.feed.getFeed({
async fetch({
cursor,
limit,
}: {
cursor: string | undefined
limit: number
}): Promise<FeedAPIResponse> {
const res = await this.agent.app.bsky.feed.getFeed({
...this.params,
cursor: this.cursor,
cursor,
limit,
})
if (res.success) {
this.cursor = res.data.cursor
// NOTE
// some custom feeds fail to enforce the pagination limit
// so we manually truncate here
Expand Down
26 changes: 12 additions & 14 deletions src/lib/api/feed/following.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
import {AppBskyFeedDefs} from '@atproto/api'
import {RootStoreModel} from 'state/index'
import {AppBskyFeedDefs, BskyAgent} from '@atproto/api'
import {FeedAPI, FeedAPIResponse} from './types'

export class FollowingFeedAPI implements FeedAPI {
cursor: string | undefined

constructor(public rootStore: RootStoreModel) {}

reset() {
this.cursor = undefined
}
constructor(public agent: BskyAgent) {}

async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.rootStore.agent.getTimeline({
const res = await this.agent.getTimeline({
limit: 1,
})
return res.data.feed[0]
}

async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
const res = await this.rootStore.agent.getTimeline({
cursor: this.cursor,
async fetch({
cursor,
limit,
}: {
cursor: string | undefined
limit: number
}): Promise<FeedAPIResponse> {
const res = await this.agent.getTimeline({
cursor,
limit,
})
if (res.success) {
this.cursor = res.data.cursor
return {
cursor: res.data.cursor,
feed: res.data.feed,
Expand Down
25 changes: 12 additions & 13 deletions src/lib/api/feed/likes.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
import {
AppBskyFeedDefs,
AppBskyFeedGetActorLikes as GetActorLikes,
BskyAgent,
} from '@atproto/api'
import {RootStoreModel} from 'state/index'
import {FeedAPI, FeedAPIResponse} from './types'

export class LikesFeedAPI implements FeedAPI {
cursor: string | undefined

constructor(
public rootStore: RootStoreModel,
public agent: BskyAgent,
public params: GetActorLikes.QueryParams,
) {}

reset() {
this.cursor = undefined
}

async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.rootStore.agent.getActorLikes({
const res = await this.agent.getActorLikes({
...this.params,
limit: 1,
})
return res.data.feed[0]
}

async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
const res = await this.rootStore.agent.getActorLikes({
async fetch({
cursor,
limit,
}: {
cursor: string | undefined
limit: number
}): Promise<FeedAPIResponse> {
const res = await this.agent.getActorLikes({
...this.params,
cursor: this.cursor,
cursor,
limit,
})
if (res.success) {
this.cursor = res.data.cursor
return {
cursor: res.data.cursor,
feed: res.data.feed,
Expand Down
25 changes: 12 additions & 13 deletions src/lib/api/feed/list.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
import {
AppBskyFeedDefs,
AppBskyFeedGetListFeed as GetListFeed,
BskyAgent,
} from '@atproto/api'
import {RootStoreModel} from 'state/index'
import {FeedAPI, FeedAPIResponse} from './types'

export class ListFeedAPI implements FeedAPI {
cursor: string | undefined

constructor(
public rootStore: RootStoreModel,
public agent: BskyAgent,
public params: GetListFeed.QueryParams,
) {}

reset() {
this.cursor = undefined
}

async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.rootStore.agent.app.bsky.feed.getListFeed({
const res = await this.agent.app.bsky.feed.getListFeed({
...this.params,
limit: 1,
})
return res.data.feed[0]
}

async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
const res = await this.rootStore.agent.app.bsky.feed.getListFeed({
async fetch({
cursor,
limit,
}: {
cursor: string | undefined
limit: number
}): Promise<FeedAPIResponse> {
const res = await this.agent.app.bsky.feed.getListFeed({
...this.params,
cursor: this.cursor,
cursor,
limit,
})
if (res.success) {
this.cursor = res.data.cursor
return {
cursor: res.data.cursor,
feed: res.data.feed,
Expand Down
Loading

0 comments on commit c8c308e

Please sign in to comment.