-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
224 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import React, { | ||
ForwardedRef, | ||
MutableRefObject, | ||
RefAttributes, | ||
forwardRef, | ||
useImperativeHandle, | ||
} from 'react'; | ||
import { FlatList } from 'react-native'; | ||
import Animated, { useAnimatedRef } from 'react-native-reanimated'; | ||
import { AnimatedContext } from '../context/AnimatedContext'; | ||
import { useLazyContextValues } from './useLazyContextValues'; | ||
|
||
export interface LazyFlatListMethods { | ||
scrollToStart: typeof FlatList.prototype.scrollToEnd; | ||
scrollToEnd: typeof FlatList.prototype.scrollToEnd; | ||
scrollToIndex: typeof FlatList.prototype.scrollToIndex; | ||
scrollToOffset: typeof FlatList.prototype.scrollToOffset; | ||
scrollToItem: typeof FlatList.prototype.scrollToItem; | ||
} | ||
|
||
export interface LazyFlatListProps { | ||
/** | ||
* How far above or below the bottom of the FlatList the threshold trigger is. Negative is above, postive it below. Accepts [FlatList props](https://reactnative.dev/docs/FlatList). | ||
* @defaultValue 0 (bottom of Fl) | ||
*/ | ||
offset?: number; | ||
/** | ||
* Ref to the LazyFlatList. Exposes scrollTo, scrollToStart, and scrollToEnd methods. | ||
*/ | ||
ref?: MutableRefObject<LazyFlatListMethods | null>; | ||
/** | ||
* When true, console.logs FlatList measurements. Even if true, will not call console.log in production. | ||
*/ | ||
debug?: boolean; | ||
} | ||
|
||
type Props<T> = LazyFlatListProps & | ||
Omit< | ||
React.ComponentProps<typeof Animated.FlatList<T>>, | ||
'onLayout' | 'onScroll' | 'ref' | ||
>; | ||
|
||
/** | ||
* LazyFlatList to wrap Lazy Children in. | ||
*/ | ||
const UnwrappedLazyFlatList = <T,>( | ||
{ offset: injectedOffset, debug = false, ...rest }: Props<T>, | ||
ref: ForwardedRef<LazyFlatListMethods> | ||
) => { | ||
const _flatListRef = useAnimatedRef<Animated.FlatList<T>>(); | ||
|
||
useImperativeHandle(ref, () => ({ | ||
scrollToStart: (options) => { | ||
_flatListRef.current?.scrollToOffset({ | ||
offset: 0, | ||
animated: options?.animated, | ||
}); | ||
}, | ||
scrollToEnd: (options) => { | ||
_flatListRef.current?.scrollToEnd(options); | ||
}, | ||
scrollToIndex: (options) => { | ||
_flatListRef.current?.scrollToIndex(options); | ||
}, | ||
scrollToOffset: (options) => { | ||
_flatListRef.current?.scrollToOffset(options); | ||
}, | ||
scrollToItem: (options) => { | ||
_flatListRef.current?.scrollToItem(options); | ||
}, | ||
})); | ||
|
||
const { value, measureScroller } = useLazyContextValues({ | ||
offset: injectedOffset, | ||
debug, | ||
horizontal: rest.horizontal, | ||
// @ts-ignore | ||
ref: _flatListRef, | ||
}); | ||
|
||
return ( | ||
<AnimatedContext.Provider value={value}> | ||
<Animated.FlatList | ||
scrollEventThrottle={16} | ||
{...rest} | ||
ref={_flatListRef} | ||
onLayout={measureScroller} | ||
/> | ||
</AnimatedContext.Provider> | ||
); | ||
}; | ||
|
||
const LazyFlatList = forwardRef(UnwrappedLazyFlatList) as <T>( | ||
props: Props<T> & RefAttributes<LazyFlatListMethods> | ||
) => React.ReactElement; | ||
|
||
export { LazyFlatList }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import React, { useCallback, useMemo } from 'react'; | ||
import { StatusBar, type LayoutChangeEvent } from 'react-native'; | ||
import Animated, { | ||
AnimatedRef, | ||
measure, | ||
runOnJS, | ||
runOnUI, | ||
useDerivedValue, | ||
useScrollViewOffset, | ||
useSharedValue, | ||
} from 'react-native-reanimated'; | ||
import { logger } from '../utils/logger'; | ||
import { LazyScrollViewProps } from './LazyScrollView'; | ||
|
||
const log = (...args: Parameters<typeof console.log>) => { | ||
logger.log('<LazyScrollView>', ...args); | ||
}; | ||
|
||
type Props = Omit<LazyScrollViewProps, 'ref'> & | ||
Pick<React.ComponentProps<typeof Animated.ScrollView>, 'horizontal'> & { | ||
ref: AnimatedRef<Animated.ScrollView>; | ||
}; | ||
|
||
/** | ||
* ScrollView to wrap Lazy Children in. | ||
*/ | ||
export const useLazyContextValues = ({ | ||
offset: injectedOffset, | ||
debug = false, | ||
horizontal, | ||
ref, | ||
}: Props) => { | ||
const _offset = useSharedValue(injectedOffset || 0); | ||
const _containerDimensions = useSharedValue({ width: 0, height: 0 }); | ||
const _containerCoordinates = useSharedValue({ x: 0, y: 0 }); | ||
const _statusBarHeight = useSharedValue(StatusBar.currentHeight || 0); | ||
const _debug = useSharedValue(debug); | ||
const _horizontal = useSharedValue(!!horizontal); | ||
|
||
/** | ||
* Starts at 0 and increases as the user scrolls down | ||
*/ | ||
const scrollValue = useScrollViewOffset(ref); | ||
|
||
const containerStart = useDerivedValue(() => | ||
_horizontal.value | ||
? _containerCoordinates.value.x | ||
: _containerCoordinates.value.y | ||
); | ||
const containerEnd = useDerivedValue( | ||
() => | ||
(_horizontal.value | ||
? _containerDimensions.value.width | ||
: _containerDimensions.value.height) + containerStart.value | ||
); | ||
|
||
const startTrigger = useDerivedValue( | ||
() => containerStart.value - _offset.value | ||
); | ||
const endTrigger = useDerivedValue(() => containerEnd.value + _offset.value); | ||
|
||
const measureScroller = useCallback( | ||
(e: LayoutChangeEvent) => { | ||
_containerDimensions.value = { | ||
height: e.nativeEvent.layout.height, | ||
width: e.nativeEvent.layout.width, | ||
}; | ||
|
||
if (debug) { | ||
log('dimensions:', { | ||
height: _containerDimensions.value.height, | ||
width: _containerDimensions.value.width, | ||
}); | ||
} | ||
|
||
// onLayout runs when RN finishes render, but native layout may not be fully settled until the next frame. | ||
requestAnimationFrame(() => { | ||
runOnUI(() => { | ||
'worklet'; | ||
const measurement = measure(ref); | ||
|
||
if (measurement) { | ||
const coordinates = { | ||
x: measurement.pageX, | ||
y: measurement.pageY + _statusBarHeight.value, | ||
}; | ||
|
||
if (_debug.value) { | ||
runOnJS(log)('coordinates:', coordinates); | ||
} | ||
|
||
_containerCoordinates.value = coordinates; | ||
} | ||
})(); | ||
}); | ||
}, | ||
// eslint-disable-next-line react-hooks/exhaustive-deps -- shared values do not trigger re-renders | ||
[] | ||
); | ||
|
||
const value = useMemo( | ||
() => ({ | ||
_hasProvider: true, | ||
scrollValue, | ||
containerStart, | ||
containerEnd, | ||
startTrigger, | ||
endTrigger, | ||
horizontal: _horizontal, | ||
}), | ||
// eslint-disable-next-line react-hooks/exhaustive-deps -- shared values do not trigger re-renders | ||
[] | ||
); | ||
|
||
return { value, measureScroller }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './components/LazyChild'; | ||
export * from './components/LazyScrollView'; | ||
export * from './components/LazyFlatList'; |