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

@ -4,36 +4,38 @@ import {
} from '@fortawesome/react-native-fontawesome'
import {useIsFocused} from '@react-navigation/native'
import {useAnalytics} from '@segment/analytics-react-native'
import {useQueryClient} from '@tanstack/react-query'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
import {ComposeIcon2} from 'lib/icons'
import {colors, s} from 'lib/styles'
import {observer} from 'mobx-react-lite'
import React from 'react'
import {FlatList, View} from 'react-native'
import {FlatList, View, useWindowDimensions} from 'react-native'
import {useStores} from 'state/index'
import {PostsFeedModel} from 'state/models/feeds/posts'
import {useHeaderOffset, POLL_FREQ} from 'view/screens/Home'
import {Feed} from '../posts/Feed'
import {TextLink} from '../util/Link'
import {FAB} from '../util/fab/FAB'
import {LoadLatestBtn} from '../util/load-latest/LoadLatestBtn'
import useAppState from 'react-native-appstate-hook'
import {logger} from '#/logger'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useSession} from '#/state/session'
export const FeedPage = observer(function FeedPageImpl({
const POLL_FREQ = 30e3 // 30sec
export function FeedPage({
testID,
isPageFocused,
feed,
feedParams,
renderEmptyState,
renderEndOfFeed,
}: {
testID?: string
feed: PostsFeedModel
feed: FeedDescriptor
feedParams?: FeedParams
isPageFocused: boolean
renderEmptyState: () => JSX.Element
renderEndOfFeed?: () => JSX.Element
@ -43,40 +45,13 @@ export const FeedPage = observer(function FeedPageImpl({
const pal = usePalette('default')
const {_} = useLingui()
const {isDesktop} = useWebMediaQueries()
const queryClient = useQueryClient()
const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll()
const {screen, track} = useAnalytics()
const headerOffset = useHeaderOffset()
const scrollElRef = React.useRef<FlatList>(null)
const {appState} = useAppState({
onForeground: () => doPoll(true),
})
const isScreenFocused = useIsFocused()
const hasNew = feed.hasNewLatest && !feed.isRefreshing
React.useEffect(() => {
// called on first load
if (!feed.hasLoaded && isPageFocused) {
feed.setup()
}
}, [isPageFocused, feed])
const doPoll = React.useCallback(
(knownActive = false) => {
if (
(!knownActive && appState !== 'active') ||
!isScreenFocused ||
!isPageFocused
) {
return
}
if (feed.isLoading) {
return
}
logger.debug('HomeScreen: Polling for new posts')
feed.checkForLatest()
},
[appState, isScreenFocused, isPageFocused, feed],
)
const [hasNew, setHasNew] = React.useState(false)
const scrollToTop = React.useCallback(() => {
scrollElRef.current?.scrollToOffset({offset: -headerOffset})
@ -86,31 +61,22 @@ export const FeedPage = observer(function FeedPageImpl({
const onSoftReset = React.useCallback(() => {
if (isPageFocused) {
scrollToTop()
feed.refresh()
queryClient.invalidateQueries({queryKey: FEED_RQKEY(feed)})
setHasNew(false)
}
}, [isPageFocused, scrollToTop, feed])
}, [isPageFocused, scrollToTop, queryClient, feed, setHasNew])
// fires when page within screen is activated/deactivated
// - check for latest
React.useEffect(() => {
if (!isPageFocused || !isScreenFocused) {
return
}
const softResetSub = store.onScreenSoftReset(onSoftReset)
const feedCleanup = feed.registerListeners()
const pollInterval = setInterval(doPoll, POLL_FREQ)
screen('Feed')
logger.debug('HomeScreen: Updating feed')
feed.checkForLatest()
return () => {
clearInterval(pollInterval)
softResetSub.remove()
feedCleanup()
}
}, [store, doPoll, onSoftReset, screen, feed, isPageFocused, isScreenFocused])
}, [store, onSoftReset, screen, feed, isPageFocused, isScreenFocused])
const onPressCompose = React.useCallback(() => {
track('HomeScreen:PressCompose')
@ -119,8 +85,9 @@ export const FeedPage = observer(function FeedPageImpl({
const onPressLoadLatest = React.useCallback(() => {
scrollToTop()
feed.refresh()
}, [feed, scrollToTop])
queryClient.invalidateQueries({queryKey: FEED_RQKEY(feed)})
setHasNew(false)
}, [scrollToTop, feed, queryClient, setHasNew])
const ListHeaderComponent = React.useCallback(() => {
if (isDesktop) {
@ -191,8 +158,12 @@ export const FeedPage = observer(function FeedPageImpl({
<Feed
testID={testID ? `${testID}-feed` : undefined}
feed={feed}
feedParams={feedParams}
enabled={isPageFocused}
pollInterval={POLL_FREQ}
scrollElRef={scrollElRef}
onScroll={onMainScroll}
onHasNew={setHasNew}
scrollEventThrottle={1}
renderEmptyState={renderEmptyState}
renderEndOfFeed={renderEndOfFeed}
@ -216,4 +187,18 @@ export const FeedPage = observer(function FeedPageImpl({
/>
</View>
)
})
}
function useHeaderOffset() {
const {isDesktop, isTablet} = useWebMediaQueries()
const {fontScale} = useWindowDimensions()
if (isDesktop) {
return 0
}
if (isTablet) {
return 50
}
// default text takes 44px, plus 34px of pad
// scale the 44px by the font scale
return 34 + 44 * fontScale
}