Replace onMainScroll with MainScrollProvider

This commit is contained in:
Dan Abramov
2023-12-13 05:04:03 +00:00
parent 7c06a4eeb9
commit 2970b1e515
5 changed files with 81 additions and 41 deletions
+35
View File
@@ -0,0 +1,35 @@
import React, {createContext, useContext, useMemo} from 'react'
import {ScrollHandlers} from 'react-native-reanimated'
const ScrollContext = createContext<ScrollHandlers<any>>({
onBeginDrag: undefined,
onEndDrag: undefined,
onScroll: undefined,
})
export function useScrollHandlers(): ScrollHandlers<any> {
return useContext(ScrollContext)
}
type ProviderProps = {children: React.ReactNode} & ScrollHandlers<any>
// Note: this completely *overrides* the parent handlers.
// It's up to you to compose them with the parent ones via useScrollHandlers() if needed.
export function ScrollProvider({
children,
onBeginDrag,
onEndDrag,
onScroll,
}: ProviderProps) {
const handlers = useMemo(
() => ({
onBeginDrag,
onEndDrag,
onScroll,
}),
[onBeginDrag, onEndDrag, onScroll],
)
return (
<ScrollContext.Provider value={handlers}>{children}</ScrollContext.Provider>
)
}
+17 -16
View File
@@ -7,7 +7,7 @@ import {useNavigation} from '@react-navigation/native'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' import {MainScrollProvider} from '../util/MainScrollProvider'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useSetMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell'
@@ -54,7 +54,6 @@ export function FeedPage({
const queryClient = useQueryClient() const queryClient = useQueryClient()
const {openComposer} = useComposerControls() const {openComposer} = useComposerControls()
const [isScrolledDown, setIsScrolledDown] = React.useState(false) const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const onMainScroll = useOnMainScroll()
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const {screen, track} = useAnalytics() const {screen, track} = useAnalytics()
const headerOffset = useHeaderOffset() const headerOffset = useHeaderOffset()
@@ -168,20 +167,22 @@ export function FeedPage({
return ( return (
<View testID={testID} style={s.h100pct}> <View testID={testID} style={s.h100pct}>
<Feed <MainScrollProvider>
testID={testID ? `${testID}-feed` : undefined} <Feed
enabled={isPageFocused} testID={testID ? `${testID}-feed` : undefined}
feed={feed} enabled={isPageFocused}
feedParams={feedParams} feed={feed}
pollInterval={POLL_FREQ} feedParams={feedParams}
scrollElRef={scrollElRef} pollInterval={POLL_FREQ}
onScrolledDownChange={setIsScrolledDown} scrollElRef={scrollElRef}
onHasNew={setHasNew} onScrolledDownChange={setIsScrolledDown}
renderEmptyState={renderEmptyState} onHasNew={setHasNew}
renderEndOfFeed={renderEndOfFeed} renderEmptyState={renderEmptyState}
ListHeaderComponent={ListHeaderComponent} renderEndOfFeed={renderEndOfFeed}
headerOffset={headerOffset} ListHeaderComponent={ListHeaderComponent}
/> headerOffset={headerOffset}
/>
</MainScrollProvider>
{(isScrolledDown || hasNew) && ( {(isScrolledDown || hasNew) && (
<LoadLatestBtn <LoadLatestBtn
onPress={onPressLoadLatest} onPress={onPressLoadLatest}
+10 -2
View File
@@ -1,6 +1,7 @@
import React, {useState} from 'react' import React, {useState} from 'react'
import {FlatListProps} from 'react-native' import {FlatListProps} from 'react-native'
import {FlatList_INTERNAL} from './Views' import {FlatList_INTERNAL} from './Views'
import {useScrollHandlers} from '#/lib/ScrollContext'
import {runOnJS} from 'react-native-reanimated' import {runOnJS} from 'react-native-reanimated'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
@@ -17,19 +18,26 @@ function ListImpl<ItemT>(
ref: React.Ref<ListMethods>, ref: React.Ref<ListMethods>,
) { ) {
const [isScrolledDown, setIsScrolledDown] = useState(false) const [isScrolledDown, setIsScrolledDown] = useState(false)
const scrollHandlers = useScrollHandlers()
function handleScrolledDownChange(didScrollDown: boolean) { function handleScrolledDownChange(didScrollDown: boolean) {
setIsScrolledDown(didScrollDown) setIsScrolledDown(didScrollDown)
onScrolledDownChange?.(didScrollDown) onScrolledDownChange?.(didScrollDown)
} }
// TODO: This ignores the passed-in onScroll completely.
const scrollHandler = useAnimatedScrollHandler({ const scrollHandler = useAnimatedScrollHandler({
onScroll(e) { onBeginDrag(e, ctx) {
scrollHandlers.onBeginDrag?.(e, ctx)
},
onEndDrag(e, ctx) {
scrollHandlers.onEndDrag?.(e, ctx)
},
onScroll(e, ctx) {
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT
if (isScrolledDown !== didScrollDown) { if (isScrolledDown !== didScrollDown) {
runOnJS(handleScrolledDownChange)(didScrollDown) runOnJS(handleScrolledDownChange)(didScrollDown)
} }
scrollHandlers.onScroll?.(e, ctx)
}, },
}) })
@@ -1,20 +1,17 @@
import {useCallback, useMemo} from 'react' import React, {useCallback} from 'react'
import {ScrollProvider} from '#/lib/ScrollContext'
import {NativeScrollEvent} from 'react-native' import {NativeScrollEvent} from 'react-native'
import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
import {useShellLayout} from '#/state/shell/shell-layout' import {useShellLayout} from '#/state/shell/shell-layout'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import { import {useSharedValue, interpolate} from 'react-native-reanimated'
useSharedValue,
interpolate,
ScrollHandlers,
} from 'react-native-reanimated'
function clamp(num: number, min: number, max: number) { function clamp(num: number, min: number, max: number) {
'worklet' 'worklet'
return Math.min(Math.max(num, min), max) return Math.min(Math.max(num, min), max)
} }
export function useOnMainScroll(): ScrollHandlers<any> { export function MainScrollProvider({children}: {children: React.ReactNode}) {
const {headerHeight} = useShellLayout() const {headerHeight} = useShellLayout()
const mode = useMinimalShellMode() const mode = useMinimalShellMode()
const setMode = useSetMinimalShellMode() const setMode = useSetMinimalShellMode()
@@ -89,14 +86,12 @@ export function useOnMainScroll(): ScrollHandlers<any> {
[headerHeight, mode, setMode, startDragOffset, startMode], [headerHeight, mode, setMode, startDragOffset, startMode],
) )
const scrollHandler: ScrollHandlers<any> = useMemo( return (
() => ({ <ScrollProvider
onBeginDrag, onBeginDrag={onBeginDrag}
onEndDrag, onEndDrag={onEndDrag}
onScroll, onScroll={onScroll}>
}), {children}
[onBeginDrag, onEndDrag, onScroll], </ScrollProvider>
) )
return scrollHandler
} }
+8 -7
View File
@@ -11,7 +11,7 @@ import {Feed} from '../com/notifications/Feed'
import {TextLink} from 'view/com/util/Link' import {TextLink} from 'view/com/util/Link'
import {ListMethods} from 'view/com/util/List' import {ListMethods} from 'view/com/util/List'
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' import {MainScrollProvider} from '../com/util/MainScrollProvider'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
@@ -36,7 +36,6 @@ type Props = NativeStackScreenProps<
export function NotificationsScreen({}: Props) { export function NotificationsScreen({}: Props) {
const {_} = useLingui() const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const onMainScroll = useOnMainScroll()
const [isScrolledDown, setIsScrolledDown] = React.useState(false) const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const scrollElRef = React.useRef<ListMethods>(null) const scrollElRef = React.useRef<ListMethods>(null)
const checkLatestRef = React.useRef<() => void | null>() const checkLatestRef = React.useRef<() => void | null>()
@@ -132,11 +131,13 @@ export function NotificationsScreen({}: Props) {
return ( return (
<View testID="notificationsScreen" style={s.hContentRegion}> <View testID="notificationsScreen" style={s.hContentRegion}>
<ViewHeader title={_(msg`Notifications`)} canGoBack={false} /> <ViewHeader title={_(msg`Notifications`)} canGoBack={false} />
<Feed <MainScrollProvider>
onScrolledDownChange={setIsScrolledDown} <Feed
scrollElRef={scrollElRef} onScrolledDownChange={setIsScrolledDown}
ListHeaderComponent={ListHeaderComponent} scrollElRef={scrollElRef}
/> ListHeaderComponent={ListHeaderComponent}
/>
</MainScrollProvider>
{(isScrolledDown || hasNew) && ( {(isScrolledDown || hasNew) && (
<LoadLatestBtn <LoadLatestBtn
onPress={onPressLoadLatest} onPress={onPressLoadLatest}