@raymardev/react-native-intersection-observer
v1.1.0
Published
A powerful React Native hook for detecting scroll intersections with ScrollView, FlatList, and SectionList components
Maintainers
Readme
React Native Intersection Observer
A powerful and flexible React Native hook for detecting scroll intersections with ScrollView, FlatList, and SectionList components. This library provides an easy-to-use API similar to the web's Intersection Observer API, but specifically designed for React Native's scroll components.
📖 Documentation • 💡 Examples • 🐛 Issues • 💬 Discussions
✨ Features
- 🎯 Multiple Detection Positions: Detect intersections at top, bottom, center, or custom element positions
- 📱 Cross-Platform: Works with ScrollView, FlatList, and SectionList components
- 🔧 TypeScript Support: Full TypeScript support with comprehensive type definitions
- ⚡ Performance Optimized: Efficient scroll event handling with minimal re-renders
- 🎨 Flexible API: Multiple convenience hooks for common use cases
- 📊 Custom Thresholds: Configurable intersection thresholds
- 🔄 Callback Support: Rich callback system for intersection events
- 🚀 Zero Dependencies: No external dependencies, lightweight and fast
- 📚 Comprehensive Docs: Detailed documentation and examples
📦 Installation
npm install @raymardev/react-native-intersection-observeror
yarn add @raymardev/react-native-intersection-observerRequirements
- React Native 0.60+
- React 16.8+ (hooks support required)
- TypeScript 4.0+ (optional but recommended)
🚀 Quick Start
import React from 'react';
import { ScrollView, Text, View } from 'react-native';
import { useScrollToBottom } from '@raymardev/react-native-intersection-observer';
export default function App() {
const { isIntersecting, ref, handleScroll } = useScrollToBottom(50, {
type: 'scrollview',
callbacks: {
onIntersect: () => console.log('Reached bottom!'),
onVisible: () => console.log('Left bottom area'),
},
});
return (
<ScrollView ref={ref} onScroll={handleScroll} scrollEventThrottle={16}>
<View style={{ height: 2000 }}>
<Text>Scroll to bottom to trigger intersection</Text>
{isIntersecting && <Text>🎉 You reached the bottom!</Text>}
</View>
</ScrollView>
);
}📚 Documentation
- API Reference - Complete API documentation
- Examples - Comprehensive examples and use cases
- Contributing - How to contribute to this project
- Troubleshooting - Common issues and solutions
- Changelog - Version history and changes
🔧 API Reference
Core Hook
useIntersectionObserver(options)
The main hook that provides intersection detection functionality.
Parameters:
options(object):threshold?: number- Distance threshold for intersection detection (default: 20)position?: 'top' | 'bottom' | 'center' | 'custom' | 'element'- Position to detect (default: 'bottom')element?: React.RefObject<View>- Element ref for position: 'element'onIntersect?: () => void- Callback when intersection startsonVisible?: () => void- Callback when intersection endsonIntersectionChange?: (isIntersecting: boolean) => void- Callback for any intersection change
Returns:
isIntersecting: boolean- Current intersection stateref: React.RefObject<T | null>- Ref for the scroll component.Tdefaults toScrollView; pass a type argument to attach it to a list, e.g.useIntersectionObserver<FlatList<Item>>({ ... })handleScroll: (event: any) => void- Scroll event handlerhandleElementLayout: (event: any) => void- Layout handler for element positioningreset: () => void- Reset intersection state
Convenience Hooks
useScrollToBottom(threshold?, options?)
Detects when the user has scrolled to the bottom of the scroll view.
const { isIntersecting, ref, handleScroll } = useScrollToBottom(50, {
type: 'scrollview', // or 'flatlist', 'sectionlist'
callbacks: {
onIntersect: () => console.log('Bottom reached!'),
},
});useScrollToTop(threshold?, options?)
Detects when the user has scrolled to the top of the scroll view.
const { isIntersecting, ref, handleScroll } = useScrollToTop(50, {
type: 'flatlist',
callbacks: {
onIntersect: () => console.log('Top reached!'),
},
});useScrollToCenter(threshold?, options?)
Detects when the user has scrolled to the center of the scroll view.
const { isIntersecting, ref, handleScroll } = useScrollToCenter(100, {
type: 'sectionlist',
callbacks: {
onIntersect: () => console.log('Center reached!'),
},
});useElementIntersection(element, threshold?, callbacks?)
Detects when a specific element becomes visible in the scroll view.
const elementRef = useRef<View>(null);
const { isIntersecting, ref, handleScroll, handleElementLayout } =
useElementIntersection(elementRef, 50, {
onIntersect: () => console.log('Element is visible!'),
});
return (
<ScrollView ref={ref} onScroll={handleScroll}>
<View ref={elementRef} onLayout={handleElementLayout}>
<Text>This element will be tracked</Text>
</View>
</ScrollView>
);💡 Common Use Cases
Infinite Scroll with FlatList
import React, { useState, useCallback } from 'react';
import { FlatList, View, Text, ActivityIndicator } from 'react-native';
import { useScrollToBottom } from '@raymardev/react-native-intersection-observer';
export default function InfiniteScrollList() {
const [data, setData] = useState(
Array.from({ length: 20 }, (_, i) => ({ id: i, text: `Item ${i}` }))
);
const [loading, setLoading] = useState(false);
const loadMore = useCallback(async () => {
if (loading) return;
setLoading(true);
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
const newData = Array.from({ length: 10 }, (_, i) => ({
id: data.length + i,
text: `Item ${data.length + i}`,
}));
setData(prev => [...prev, ...newData]);
setLoading(false);
}, [data.length, loading]);
const { ref, handleScroll } = useScrollToBottom(100, {
type: 'flatlist',
callbacks: {
onIntersect: loadMore,
},
});
const renderItem = ({ item }: { item: any }) => (
<View style={{ padding: 20, borderBottomWidth: 1 }}>
<Text>{item.text}</Text>
</View>
);
const renderFooter = () =>
loading ? <ActivityIndicator style={{ padding: 20 }} /> : null;
return (
<FlatList
ref={ref}
data={data}
renderItem={renderItem}
onScroll={handleScroll}
scrollEventThrottle={16}
ListFooterComponent={renderFooter}
/>
);
}Pull-to-Refresh Detection
import React from 'react';
import { ScrollView, View, Text } from 'react-native';
import { useScrollToTop } from '@raymardev/react-native-intersection-observer';
export default function PullToRefresh() {
const { isIntersecting, ref, handleScroll } = useScrollToTop(10, {
type: 'scrollview',
callbacks: {
onIntersect: () => {
console.log('Ready for pull-to-refresh!');
// Trigger your refresh logic here
},
},
});
return (
<ScrollView ref={ref} onScroll={handleScroll} scrollEventThrottle={16}>
<View style={{ height: 2000 }}>
<Text>Pull down to refresh</Text>
{isIntersecting && <Text>🔄 Ready to refresh!</Text>}
</View>
</ScrollView>
);
}Element Visibility Tracking
import React, { useRef } from 'react';
import { ScrollView, View, Text } from 'react-native';
import { useElementIntersection } from '@raymardev/react-native-intersection-observer';
export default function ElementTracking() {
const targetRef = useRef<View>(null);
const { isIntersecting, ref, handleScroll, handleElementLayout } =
useElementIntersection(targetRef, 50, {
onIntersect: () => console.log('Target element is visible!'),
onVisible: () => console.log('Target element is hidden!'),
});
return (
<ScrollView ref={ref} onScroll={handleScroll} scrollEventThrottle={16}>
<View style={{ height: 1000 }}>
<Text>Scroll down to see the target element</Text>
</View>
<View
ref={targetRef}
onLayout={handleElementLayout}
style={{
height: 200,
backgroundColor: isIntersecting ? '#4CAF50' : '#FF5722',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Text style={{ color: 'white', fontSize: 18 }}>
{isIntersecting ? 'I am visible! 🎉' : 'I am hidden 👻'}
</Text>
</View>
<View style={{ height: 1000 }}>
<Text>More content below</Text>
</View>
</ScrollView>
);
}🧭 How this relates to React Native's own APIs
React Native covers part of this ground already, and it is worth knowing which tool fits your case:
FlatList.onEndReachedis the better choice for plain "load the next page" infinite scroll. It works with virtualization and needs noonScrollwiring.onViewableItemsChanged+viewabilityConfigis the better choice for tracking which list items are visible, and it offers percentage thresholds andminimumViewTime, which this library does not.IntersectionObserverlanded natively in React Native 0.83, but only at the canary release level behind a feature flag — not in stable React Native, and not in Expo.
What this library adds is scroll-position detection — top, bottom, center and
custom predicates over the scroll geometry — which the Intersection Observer model
does not express, plus element tracking that works on stable React Native today.
When the platform observer is present it can be used for element tracking via the
strategy option; see the API reference.
Naming note:
@react-navigation/nativealso exports auseScrollToTop, and it does the opposite of this one — it scrolls to the top, while this one detects the top. If you use both, import one under an alias.
📐 Element tracking accuracy
onLayout reports coordinates relative to the tracked view's parent, not to
the scroll content, so those two only agree when the view is a direct child of the
scroll view. useElementIntersection therefore measures the view against the
scroll container itself and falls back to onLayout only when that is impossible.
For this to work, attach the hook's ref to your scroll component and pass the
tracked view's ref as element. Nested elements are then measured correctly:
const targetRef = useRef<View>(null);
const { isIntersecting, ref, handleScroll, handleElementLayout } =
useElementIntersection(targetRef, 50);
return (
<ScrollView ref={ref} onScroll={handleScroll} scrollEventThrottle={16}>
<View style={{ padding: 24 }}>
{/* Nested as deeply as you like. */}
<View ref={targetRef} onLayout={handleElementLayout} />
</View>
</ScrollView>
);Call the returned measureElement() after any layout change the hook cannot
observe on its own.
⚡ Performance Considerations
- Use
scrollEventThrottle={16}for smooth 60fps scroll events - The hook automatically optimizes re-renders by only updating state when intersection status changes
- For large lists, consider using the convenience hooks which are optimized for specific use cases
- Element intersection tracking requires layout measurements, so use sparingly for performance
🔧 TypeScript Support
This library is written in TypeScript and provides full type definitions. All hooks and their parameters are fully typed for the best development experience.
import {
useIntersectionObserver,
UseIntersectionObserverOptions,
UseIntersectionObserverReturn,
} from '@raymardev/react-native-intersection-observer';
const options: UseIntersectionObserverOptions = {
threshold: 50,
position: 'bottom',
onIntersect: () => console.log('Intersected!'),
};
const result: UseIntersectionObserverReturn = useIntersectionObserver(options);👨💻 About the Author
This library is created and maintained by Ray Martin, a full-stack developer specializing in React Native, Next.js, and modern web technologies.
- 🌐 Personal Website: raymartin.es (available in English and Spanish)
- 💼 Professional Profile: Full-stack developer with expertise in enterprise-grade applications
- 🚀 Open Source: Active contributor to the React Native ecosystem
- 📧 Contact: Available for consulting and collaboration
🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details on how to get started.
Quick Start for Contributors
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes and add tests
- Run tests:
npm test - Commit your changes:
git commit -m 'feat: add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
🐛 Issues & Support
- Bug Reports: Open an issue
- Feature Requests: Start a discussion
- Questions: Join the discussion
- Troubleshooting: Check our Troubleshooting Guide
📄 License
MIT © Ray Martin • GitHub
📈 Changelog
See CHANGELOG.md for a complete list of changes.
Recent Updates
- v1.0.0 - Initial release with full feature set
- Comprehensive documentation and examples
- TypeScript support with full type definitions
- Performance optimizations
- Cross-platform compatibility
⭐ Star this repository if you find it helpful!
🌟 Connect with the Author
Ray Martin • Full-Stack Developer
Available for consulting and collaboration • Website available in English and Spanish
Made with ❤️ by Ray Martin
