Provides an api to react to allow child components of ScrollView or FlatList to easily manage their visibility states on their own, instead of at a list level. This is done by measuring children on the UI thread and only firing back to JS when a defined threshold has been passed.
yarn add react-native-lazy-scrollviewThis library requires reanimated. Follow their installation instructions.
- Swap out
ScrollVieworFlatlistforLazyScrollVieworLazyFlatlist. - Wrap any child of the list that you'd like to react to visibility in
LazyChild. - Define
LazyChild's callbacks.entering/exitingcallbacks fire when the child passes the parent list's threshold when compared to its edge (default is 0, meaning top and bottom of the list).visibilitycallbacks fire when the child has itspercentVisibleThresholdmet (defaults to1, or 100%). You can add aminimumVisibilityMsvalue to delay/prevent firing avisbilitycallback until it has been visible for a certain amount of time.
- If a
LazyChilddoes not have aLazyScrollview/LazyFlatlistancestor, it will fire itsonEnterThresholdPassandonVisibilityEnteron mount. - If a corresponding 'exit' callback is not provided for an 'enter' callback, after the 'enter' callback fires, measuring stops for that callback.
scrollEventThrottledefaults to 16.
Extends ScrollView props, omitting onLayout and ref (see below).
| Name | Type | Required | Description |
|---|---|---|---|
| offset | number |
No | How far above or below the bottom of the ScrollView the threshold trigger is. Negative = above, positive = below. Default: 0 (bottom of ScrollView). |
| ref | MutableRefObject<LazyScrollViewMethods> |
No | Ref to the LazyScrollView. Exposes scrollTo, scrollToStart, and scrollToEnd methods. |
| debug | boolean |
No | When true, logs measurement data for debugging. Logs are disabled in production. |
| Method | Type | Description |
|---|---|---|
| scrollTo | Maps to ScrollView scrollTo | Scrolls to a specific x/y offset. |
| scrollToStart | (options?: { animated: boolean }): void |
Scrolls to the start of the list. |
| scrollToEnd | (options?: { animated: boolean }): void |
Scrolls to the end of the list. |
Extends Flatlist props, omitting onLayout and ref (see below).
| Name | Type | Required | Description |
|---|---|---|---|
| offset | number |
No | How far above or below the bottom of the FlatList the threshold trigger is. Negative = above, positive = below. Default: 0 (bottom of FlatList). |
| ref | MutableRefObject<LazyFlatListMethods | null> |
No | Ref to the LazyFlatList. Exposes scrollToStart, scrollToEnd, scrollToIndex, scrollToOffset, and scrollToItem methods. |
| debug | boolean |
No | When true, logs measurement data for debugging. Logs are disabled in production. |
| Method | Type | Description |
|---|---|---|
| scrollToStart | (options?: { animated: boolean }): void |
Scrolls to the start (top/left) of the list. |
| scrollToEnd | (options?: { animated: boolean }): void |
Scrolls to the end (bottom/right) of the list. |
| scrollToIndex | Maps to Flatlist scrollToIndex | Scrolls to the item at the given index. |
| scrollToOffset | Maps to Flatlist scrollToOffset | Scrolls to a specific offset. |
| scrollToItem | Maps to Flatlist scrollToItem | Scrolls to a specific item. |
| Name | Type | Required | Description |
|---|---|---|---|
| onEnterThresholdPass | () => void |
No | Fires when the child passes the scroll offset after being offscreen. Only fires once and stops measuring if onExitThresholdPass is not provided. |
| onExitThresholdPass | () => void |
No | Fires when the child passes the scroll offset after being onscreen. Will not fire if onEnterThresholdPass has not fired. |
| onVisibilityEnter | () => void |
No | Fires when the child’s viewable area exceeds the percentVisibleThreshold. Only fires once and stops measuring if onVisibilityExit is not provided. |
| onVisibilityExit | () => void |
No | Fires when the child’s viewable area goes under the percentVisibleThreshold after being above it. Will not fire if onVisibilityEnter has not fired. |
| children | React.ReactNode |
Yes | The child component(s) to wrap. |
| percentVisibleThreshold | number |
No | Fraction of the child that must be visible before onVisibilityEnter fires. Example: 0.5 = 50% visible. Default: 1.0. |
| minimumVisibilityMs | number |
No | Minimum time (ms) the child must remain visible before onVisibilityEnter fires. If undefined, fires immediately. |
| debug | boolean |
No | When true, logs measurement data for debugging. Logs are disabled in production. |
// MyCoolHomeScreen.tsx
import { LazyScrollView } from 'react-native-lazy-scrollview';
import {
CoolComponentA,
CoolComponentB,
CoolComponentC,
PriceMasterVideo,
ScrollToTopButton,
} from './components';
export function MyCoolHomeScreen() {
const ref = useRef < LazyScrollViewMethods > null;
return (
// Trigger entering/exiting callbacks when child is 300 pixels outside of scrollview's bounds
<LazyScrollView offset={300} showsVerticalScrollIndicator={false}>
<CoolComponentA />
<PriceMasterVideo />
<CoolComponentB />
<CoolComponentC />
<ScrollToTopButton
onPress={() => ref.current?.scrollToStart({ animated: true })}
/>
</LazyScrollView>
);
}
// CoolComponentC.tsx
import { View } from 'react-native';
import { LazyChild } from 'react-native-lazy-scrollview';
import { ContentView, SkeletonLoader } from './components';
export function CoolComponentC() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const onEnterThresholdPass = useCallback(async () => {
try {
const fetchedData = await someExpensiveApiCall();
setData(fetchedData);
setLoading(false);
} catch (e) {
setLoading(false);
}
}, []);
// Fired when LazyChild has 75% visibility for over 250 ms
const onVisibilityEnter = useCallback(async () => {
analyticsCall();
}, []);
if (!loading && !data) {
// Trigger has fired and no data :(
return null;
}
return (
<LazyChild
onEnterThresholdPass={onEnterThresholdPass}
onVisibilityEnter={onVisibilityEnter}
percentVisibleThreshold={0.75}
minimumVisibilityMs={250}
>
{loading ? <SkeletonLoader /> : <ContentView data={data} />}
</LazyChild>
);
}
// PriceMasterVideo.tsx
import { View } from 'react-native';
import { LazyChild } from 'react-native-lazy-scrollview';
import { ContentView, SkeletonLoader, VideoPlayer } from './components';
const videoURl = 'https://youtu.be/wfJnni0oBPE?si=kRdIUcq4l5dfGfqV';
export function PriceMasterVideo() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [paused, setPaused] = useState(true);
const onEnterThresholdPass = useCallback(async () => {
setLoading(false);
}, []);
// Fired when LazyChild has 25% visibility
const onVisibilityEnter = useCallback(async () => {
analyticsCall();
setPaused(false);
}, []);
// Fired when LazyChild is less that 25% visible after being visible
const onVisibilityExit = useCallback(async () => {
setPaused(true);
}, []);
return (
<LazyChild
onEnterThresholdPass={onEnterThresholdPass}
onVisibilityEnter={onVisibilityEnter}
onVisibilityExit={onVisibilityExit}
percentVisibleThreshold={0.25}
>
{loading ? (
<SkeletonLoader />
) : (
<VideoPlayer paused={paused} videoUrl={videoUrl} />
)}
</LazyChild>
);
}To run the example app, clone the repo
cd example
yarn install
yarn ios
# or
yarn androidMIT
Made with create-react-native-library