Refactor feeds to use react-query (#1862)

* 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
This commit is contained in:
Paul Frazee 2023-11-10 15:34:25 -08:00 committed by GitHub
parent 51f04b9620
commit c8c308e31e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 904 additions and 1081 deletions

View file

@ -1,24 +1,40 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {PostsFeedSliceModel} from 'state/models/feeds/posts-slice'
import {AtUri} from '@atproto/api'
import {FeedPostSlice} from '#/state/queries/post-feed'
import {AtUri, moderatePost} from '@atproto/api'
import {Link} from '../util/Link'
import {Text} from '../util/text/Text'
import Svg, {Circle, Line} from 'react-native-svg'
import {FeedItem} from './FeedItem'
import {usePalette} from 'lib/hooks/usePalette'
import {makeProfileLink} from 'lib/routes/links'
import {useStores} from '#/state'
export const FeedSlice = observer(function FeedSliceImpl({
slice,
dataUpdatedAt,
ignoreFilterFor,
}: {
slice: PostsFeedSliceModel
slice: FeedPostSlice
dataUpdatedAt: number
ignoreFilterFor?: string
}) {
if (slice.shouldFilter(ignoreFilterFor)) {
return null
const store = useStores()
const moderations = React.useMemo(() => {
return slice.items.map(item =>
moderatePost(item.post, store.preferences.moderationOpts),
)
}, [slice, store.preferences.moderationOpts])
// apply moderation filter
for (let i = 0; i < slice.items.length; i++) {
if (
moderations[i]?.content.filter &&
slice.items[i].post.author.did !== ignoreFilterFor
) {
return null
}
}
if (slice.isThread && slice.items.length > 3) {
@ -27,23 +43,34 @@ export const FeedSlice = observer(function FeedSliceImpl({
<>
<FeedItem
key={slice.items[0]._reactKey}
item={slice.items[0]}
source={slice.source}
isThreadParent={slice.isThreadParentAt(0)}
isThreadChild={slice.isThreadChildAt(0)}
post={slice.items[0].post}
record={slice.items[0].record}
reason={slice.items[0].reason}
moderation={moderations[0]}
dataUpdatedAt={dataUpdatedAt}
isThreadParent={isThreadParentAt(slice.items, 0)}
isThreadChild={isThreadChildAt(slice.items, 0)}
/>
<FeedItem
key={slice.items[1]._reactKey}
item={slice.items[1]}
isThreadParent={slice.isThreadParentAt(1)}
isThreadChild={slice.isThreadChildAt(1)}
post={slice.items[1].post}
record={slice.items[1].record}
reason={slice.items[1].reason}
moderation={moderations[1]}
dataUpdatedAt={dataUpdatedAt}
isThreadParent={isThreadParentAt(slice.items, 1)}
isThreadChild={isThreadChildAt(slice.items, 1)}
/>
<ViewFullThread slice={slice} />
<FeedItem
key={slice.items[last]._reactKey}
item={slice.items[last]}
isThreadParent={slice.isThreadParentAt(last)}
isThreadChild={slice.isThreadChildAt(last)}
post={slice.items[last].post}
record={slice.items[last].record}
reason={slice.items[last].reason}
moderation={moderations[last]}
dataUpdatedAt={dataUpdatedAt}
isThreadParent={isThreadParentAt(slice.items, last)}
isThreadChild={isThreadChildAt(slice.items, last)}
isThreadLastChild
/>
</>
@ -55,12 +82,15 @@ export const FeedSlice = observer(function FeedSliceImpl({
{slice.items.map((item, i) => (
<FeedItem
key={item._reactKey}
item={item}
source={i === 0 ? slice.source : undefined}
isThreadParent={slice.isThreadParentAt(i)}
isThreadChild={slice.isThreadChildAt(i)}
post={slice.items[i].post}
record={slice.items[i].record}
reason={slice.items[i].reason}
moderation={moderations[i]}
dataUpdatedAt={dataUpdatedAt}
isThreadParent={isThreadParentAt(slice.items, i)}
isThreadChild={isThreadChildAt(slice.items, i)}
isThreadLastChild={
slice.isThreadChildAt(i) && slice.items.length === i + 1
isThreadChildAt(slice.items, i) && slice.items.length === i + 1
}
/>
))}
@ -68,12 +98,12 @@ export const FeedSlice = observer(function FeedSliceImpl({
)
})
function ViewFullThread({slice}: {slice: PostsFeedSliceModel}) {
function ViewFullThread({slice}: {slice: FeedPostSlice}) {
const pal = usePalette('default')
const itemHref = React.useMemo(() => {
const urip = new AtUri(slice.rootItem.post.uri)
return makeProfileLink(slice.rootItem.post.author, 'post', urip.rkey)
}, [slice.rootItem.post.uri, slice.rootItem.post.author])
const urip = new AtUri(slice.rootUri)
return makeProfileLink({did: urip.hostname, handle: ''}, 'post', urip.rkey)
}, [slice.rootUri])
return (
<Link
@ -115,3 +145,17 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
})
function isThreadParentAt<T>(arr: Array<T>, i: number) {
if (arr.length === 1) {
return false
}
return i < arr.length - 1
}
function isThreadChildAt<T>(arr: Array<T>, i: number) {
if (arr.length === 1) {
return false
}
return i > 0
}