Fix <List> types (#6756)

* fix List and ScrollView types

* add comment

* rm omitting ref
This commit is contained in:
Samuel Newman
2024-11-26 15:55:27 +00:00
committed by GitHub
parent 56a88098d2
commit ce1b04b925
10 changed files with 171 additions and 155 deletions
@@ -1,5 +1,5 @@
import React, {useCallback, useRef} from 'react' import React, {useCallback, useRef} from 'react'
import {FlatList, LayoutChangeEvent, View} from 'react-native' import {LayoutChangeEvent, View} from 'react-native'
import { import {
KeyboardStickyView, KeyboardStickyView,
useKeyboardHandler, useKeyboardHandler,
@@ -33,7 +33,7 @@ import {
EmojiPicker, EmojiPicker,
EmojiPickerState, EmojiPickerState,
} from '#/view/com/composer/text-input/web/EmojiPicker.web' } from '#/view/com/composer/text-input/web/EmojiPicker.web'
import {List} from '#/view/com/util/List' import {List, ListMethods} from '#/view/com/util/List'
import {ChatDisabled} from '#/screens/Messages/components/ChatDisabled' import {ChatDisabled} from '#/screens/Messages/components/ChatDisabled'
import {MessageInput} from '#/screens/Messages/components/MessageInput' import {MessageInput} from '#/screens/Messages/components/MessageInput'
import {MessageListError} from '#/screens/Messages/components/MessageListError' import {MessageListError} from '#/screens/Messages/components/MessageListError'
@@ -94,7 +94,7 @@ export function MessagesList({
const getPost = useGetPost() const getPost = useGetPost()
const {embedUri, setEmbed} = useMessageEmbed() const {embedUri, setEmbed} = useMessageEmbed()
const flatListRef = useAnimatedRef<FlatList>() const flatListRef = useAnimatedRef<ListMethods>()
const [newMessagesPill, setNewMessagesPill] = React.useState({ const [newMessagesPill, setNewMessagesPill] = React.useState({
show: false, show: false,
+2 -1
View File
@@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import Animated from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -35,7 +36,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const onboardDispatch = useOnboardingDispatch() const onboardDispatch = useOnboardingDispatch()
const {state, dispatch} = React.useContext(Context) const {state, dispatch} = React.useContext(Context)
const scrollview = React.useRef<ScrollView>(null) const scrollview = React.useRef<Animated.ScrollView>(null)
const prevActiveStep = React.useRef<string>(state.activeStep) const prevActiveStep = React.useRef<string>(state.activeStep)
React.useEffect(() => { React.useEffect(() => {
+2 -2
View File
@@ -1,5 +1,5 @@
import * as React from 'react' import * as React from 'react'
import {FlatList, ScrollView, StyleSheet, View} from 'react-native' import {ScrollView, StyleSheet, View} from 'react-native'
import {useAnimatedRef} from 'react-native-reanimated' import {useAnimatedRef} from 'react-native-reanimated'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
@@ -11,7 +11,7 @@ import {TabBar} from './TabBar'
export interface PagerWithHeaderChildParams { export interface PagerWithHeaderChildParams {
headerHeight: number headerHeight: number
isFocused: boolean isFocused: boolean
scrollElRef: React.MutableRefObject<FlatList<any> | ScrollView | null> scrollElRef: React.MutableRefObject<ListMethods | ScrollView | null>
} }
export interface PagerWithHeaderProps { export interface PagerWithHeaderProps {
+129 -117
View File
@@ -1,6 +1,10 @@
import React, {memo} from 'react' import React, {memo} from 'react'
import {FlatListProps, RefreshControl, ViewToken} from 'react-native' import {RefreshControl, ViewToken} from 'react-native'
import {runOnJS, useSharedValue} from 'react-native-reanimated' import {
FlatListPropsWithLayout,
runOnJS,
useSharedValue,
} from 'react-native-reanimated'
import {updateActiveVideoViewAsync} from '@haileyok/bluesky-video' import {updateActiveVideoViewAsync} from '@haileyok/bluesky-video'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
@@ -13,8 +17,8 @@ import {useTheme} from '#/alf'
import {FlatList_INTERNAL} from './Views' import {FlatList_INTERNAL} from './Views'
export type ListMethods = FlatList_INTERNAL export type ListMethods = FlatList_INTERNAL
export type ListProps<ItemT> = Omit< export type ListProps<ItemT = any> = Omit<
FlatListProps<ItemT>, FlatListPropsWithLayout<ItemT>,
| 'onMomentumScrollBegin' // Use ScrollContext instead. | 'onMomentumScrollBegin' // Use ScrollContext instead.
| 'onMomentumScrollEnd' // Use ScrollContext instead. | 'onMomentumScrollEnd' // Use ScrollContext instead.
| 'onScroll' // Use ScrollContext instead. | 'onScroll' // Use ScrollContext instead.
@@ -22,6 +26,7 @@ export type ListProps<ItemT> = Omit<
| 'onScrollEndDrag' // Use ScrollContext instead. | 'onScrollEndDrag' // Use ScrollContext instead.
| 'refreshControl' // Pass refreshing and/or onRefresh instead. | 'refreshControl' // Pass refreshing and/or onRefresh instead.
| 'contentOffset' // Pass headerOffset instead. | 'contentOffset' // Pass headerOffset instead.
| 'progressViewOffset' // Can't be an animated value
> & { > & {
onScrolledDownChange?: (isScrolledDown: boolean) => void onScrolledDownChange?: (isScrolledDown: boolean) => void
headerOffset?: number headerOffset?: number
@@ -32,130 +37,137 @@ export type ListProps<ItemT> = Omit<
// Web only prop to contain the scroll to the container rather than the window // Web only prop to contain the scroll to the container rather than the window
disableFullWindowScroll?: boolean disableFullWindowScroll?: boolean
sideBorders?: boolean sideBorders?: boolean
progressViewOffset?: number
} }
export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null> export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null>
const SCROLLED_DOWN_LIMIT = 200 const SCROLLED_DOWN_LIMIT = 200
function ListImpl<ItemT>( let List = React.forwardRef<ListMethods, ListProps>(
{ (
onScrolledDownChange, {
refreshing, onScrolledDownChange,
onRefresh, refreshing,
onItemSeen, onRefresh,
headerOffset, onItemSeen,
style, headerOffset,
progressViewOffset, style,
...props progressViewOffset,
}: ListProps<ItemT>, ...props
ref: React.Ref<ListMethods>,
) {
const isScrolledDown = useSharedValue(false)
const t = useTheme()
const dedupe = useDedupe(400)
const {activeLightbox} = useLightbox()
function handleScrolledDownChange(didScrollDown: boolean) {
onScrolledDownChange?.(didScrollDown)
}
// Intentionally destructured outside the main thread closure.
// See https://github.com/bluesky-social/social-app/pull/4108.
const {
onBeginDrag: onBeginDragFromContext,
onEndDrag: onEndDragFromContext,
onScroll: onScrollFromContext,
onMomentumEnd: onMomentumEndFromContext,
} = useScrollHandlers()
const scrollHandler = useAnimatedScrollHandler({
onBeginDrag(e, ctx) {
onBeginDragFromContext?.(e, ctx)
}, },
onEndDrag(e, ctx) { ref,
runOnJS(updateActiveVideoViewAsync)() ): React.ReactElement => {
onEndDragFromContext?.(e, ctx) const isScrolledDown = useSharedValue(false)
}, const t = useTheme()
onScroll(e, ctx) { const dedupe = useDedupe(400)
onScrollFromContext?.(e, ctx) const {activeLightbox} = useLightbox()
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT function handleScrolledDownChange(didScrollDown: boolean) {
if (isScrolledDown.get() !== didScrollDown) { onScrolledDownChange?.(didScrollDown)
isScrolledDown.set(didScrollDown)
if (onScrolledDownChange != null) {
runOnJS(handleScrolledDownChange)(didScrollDown)
}
}
if (isIOS) {
runOnJS(dedupe)(updateActiveVideoViewAsync)
}
},
// Note: adding onMomentumBegin here makes simulator scroll
// lag on Android. So either don't add it, or figure out why.
onMomentumEnd(e, ctx) {
runOnJS(updateActiveVideoViewAsync)()
onMomentumEndFromContext?.(e, ctx)
},
})
const [onViewableItemsChanged, viewabilityConfig] = React.useMemo(() => {
if (!onItemSeen) {
return [undefined, undefined]
} }
return [
(info: {viewableItems: Array<ViewToken>; changed: Array<ViewToken>}) => { // Intentionally destructured outside the main thread closure.
for (const item of info.changed) { // See https://github.com/bluesky-social/social-app/pull/4108.
if (item.isViewable) { const {
onItemSeen(item.item) onBeginDrag: onBeginDragFromContext,
onEndDrag: onEndDragFromContext,
onScroll: onScrollFromContext,
onMomentumEnd: onMomentumEndFromContext,
} = useScrollHandlers()
const scrollHandler = useAnimatedScrollHandler({
onBeginDrag(e, ctx) {
onBeginDragFromContext?.(e, ctx)
},
onEndDrag(e, ctx) {
runOnJS(updateActiveVideoViewAsync)()
onEndDragFromContext?.(e, ctx)
},
onScroll(e, ctx) {
onScrollFromContext?.(e, ctx)
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT
if (isScrolledDown.get() !== didScrollDown) {
isScrolledDown.set(didScrollDown)
if (onScrolledDownChange != null) {
runOnJS(handleScrolledDownChange)(didScrollDown)
} }
} }
},
{
itemVisiblePercentThreshold: 40,
minimumViewTime: 0.5e3,
},
]
}, [onItemSeen])
let refreshControl if (isIOS) {
if (refreshing !== undefined || onRefresh !== undefined) { runOnJS(dedupe)(updateActiveVideoViewAsync)
refreshControl = ( }
<RefreshControl },
refreshing={refreshing ?? false} // Note: adding onMomentumBegin here makes simulator scroll
onRefresh={onRefresh} // lag on Android. So either don't add it, or figure out why.
tintColor={t.atoms.text.color} onMomentumEnd(e, ctx) {
titleColor={t.atoms.text.color} runOnJS(updateActiveVideoViewAsync)()
progressViewOffset={progressViewOffset ?? headerOffset} onMomentumEndFromContext?.(e, ctx)
},
})
const [onViewableItemsChanged, viewabilityConfig] = React.useMemo(() => {
if (!onItemSeen) {
return [undefined, undefined]
}
return [
(info: {
viewableItems: Array<ViewToken>
changed: Array<ViewToken>
}) => {
for (const item of info.changed) {
if (item.isViewable) {
onItemSeen(item.item)
}
}
},
{
itemVisiblePercentThreshold: 40,
minimumViewTime: 0.5e3,
},
]
}, [onItemSeen])
let refreshControl
if (refreshing !== undefined || onRefresh !== undefined) {
refreshControl = (
<RefreshControl
refreshing={refreshing ?? false}
onRefresh={onRefresh}
tintColor={t.atoms.text.color}
titleColor={t.atoms.text.color}
progressViewOffset={progressViewOffset ?? headerOffset}
/>
)
}
let contentOffset
if (headerOffset != null) {
style = addStyle(style, {
paddingTop: headerOffset,
})
contentOffset = {x: 0, y: headerOffset * -1}
}
return (
<FlatList_INTERNAL
{...props}
scrollIndicatorInsets={{right: 1}}
contentOffset={contentOffset}
refreshControl={refreshControl}
onScroll={scrollHandler}
scrollsToTop={!activeLightbox}
scrollEventThrottle={1}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
showsVerticalScrollIndicator={!isAndroid}
style={style}
// @ts-expect-error FlatList_INTERNAL ref type is wrong -sfn
ref={ref}
/> />
) )
} },
)
List.displayName = 'List'
let contentOffset List = memo(List)
if (headerOffset != null) { export {List}
style = addStyle(style, {
paddingTop: headerOffset,
})
contentOffset = {x: 0, y: headerOffset * -1}
}
return (
<FlatList_INTERNAL
{...props}
scrollIndicatorInsets={{right: 1}}
contentOffset={contentOffset}
refreshControl={refreshControl}
onScroll={scrollHandler}
scrollsToTop={!activeLightbox}
scrollEventThrottle={1}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
showsVerticalScrollIndicator={!isAndroid}
style={style}
ref={ref}
/>
)
}
export const List = memo(React.forwardRef(ListImpl)) as <ItemT>(
props: ListProps<ItemT> & {ref?: React.Ref<ListMethods>},
) => React.ReactElement
+1
View File
@@ -113,6 +113,7 @@ export const ViewSelector = React.forwardRef<
) )
return ( return (
<FlatList_INTERNAL <FlatList_INTERNAL
// @ts-expect-error FlatList_INTERNAL ref type is wrong -sfn
ref={flatListRef} ref={flatListRef}
data={data} data={data}
keyExtractor={keyExtractor} keyExtractor={keyExtractor}
-19
View File
@@ -1,19 +0,0 @@
import React from 'react'
import {ViewProps} from 'react-native'
export {FlatList as FlatList_INTERNAL, ScrollView} from 'react-native'
export function CenteredView({
style,
sideBorders,
...props
}: React.PropsWithChildren<
ViewProps & {
/**
* @platform web
*/
sideBorders?: boolean
/**
* @platform web
*/
topBorder?: boolean
}
>)
-7
View File
@@ -1,7 +0,0 @@
import {View} from 'react-native'
import Animated from 'react-native-reanimated'
// If you explode these into functions, don't forget to forwardRef!
export const FlatList_INTERNAL = Animated.FlatList
export const CenteredView = View
export const ScrollView = Animated.ScrollView
+28
View File
@@ -0,0 +1,28 @@
import {forwardRef} from 'react'
import {FlatListComponent} from 'react-native'
import {View, ViewProps} from 'react-native'
import Animated from 'react-native-reanimated'
import {FlatListPropsWithLayout} from 'react-native-reanimated'
// If you explode these into functions, don't forget to forwardRef!
/**
* Avoid using `FlatList_INTERNAL` and use `List` where possible.
* The types are a bit wrong on `FlatList_INTERNAL`
*/
export const FlatList_INTERNAL = Animated.FlatList
export type FlatList_INTERNAL<ItemT = any> = Omit<
FlatListComponent<ItemT, FlatListPropsWithLayout<ItemT>>,
'CellRendererComponent'
>
export const ScrollView = Animated.ScrollView
export type ScrollView = typeof Animated.ScrollView
export const CenteredView = forwardRef<
View,
React.PropsWithChildren<
ViewProps & {sideBorders?: boolean; topBorder?: boolean}
>
>(function CenteredView(props, ref) {
return <View ref={ref} {...props} />
})
+3 -3
View File
@@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import {ActivityIndicator, type FlatList, StyleSheet, View} from 'react-native' import {ActivityIndicator, StyleSheet, View} from 'react-native'
import {AppBskyFeedDefs} from '@atproto/api' import {AppBskyFeedDefs} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -25,7 +25,7 @@ import {useComposerControls} from '#/state/shell/composer'
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
import {FAB} from '#/view/com/util/fab/FAB' import {FAB} from '#/view/com/util/fab/FAB'
import {TextLink} from '#/view/com/util/Link' import {TextLink} from '#/view/com/util/Link'
import {List} from '#/view/com/util/List' import {List, ListMethods} from '#/view/com/util/List'
import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {Text} from '#/view/com/util/text/Text' import {Text} from '#/view/com/util/text/Text'
import {ViewHeader} from '#/view/com/util/ViewHeader' import {ViewHeader} from '#/view/com/util/ViewHeader'
@@ -130,7 +130,7 @@ export function FeedsScreen(_props: Props) {
error: searchError, error: searchError,
} = useSearchPopularFeedsMutation() } = useSearchPopularFeedsMutation()
const {hasSession} = useSession() const {hasSession} = useSession()
const listRef = React.useRef<FlatList>(null) const listRef = React.useRef<ListMethods>(null)
/** /**
* A search query is present. We may not have search results yet. * A search query is present. We may not have search results yet.
+3 -3
View File
@@ -1,15 +1,15 @@
import React from 'react' import React from 'react'
import {FlatList, View} from 'react-native' import {View} from 'react-native'
import {ScrollProvider} from '#/lib/ScrollContext' import {ScrollProvider} from '#/lib/ScrollContext'
import {List} from '#/view/com/util/List' import {List, ListMethods} from '#/view/com/util/List'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
import * as Toggle from '#/components/forms/Toggle' import * as Toggle from '#/components/forms/Toggle'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
export function ListContained() { export function ListContained() {
const [animated, setAnimated] = React.useState(false) const [animated, setAnimated] = React.useState(false)
const ref = React.useRef<FlatList>(null) const ref = React.useRef<ListMethods>(null)
const data = React.useMemo(() => { const data = React.useMemo(() => {
return Array.from({length: 100}, (_, i) => ({ return Array.from({length: 100}, (_, i) => ({