children: React.ReactNode
The child components that will have access to the scroll context.
The ScrollToHideProvider
is a React context provider that manages scroll-to-hide animations for headers and tab bars. It provides smooth animations that hide/show UI elements based on scroll direction and position.
import { RNCProvider } from 'rnc-theme';
function App() {return ( <RNCProvider scrollToHideProps={{ headerHeight: 100, tabBarHeight: 60, }} > <YourScreenContent /> </RNCProvider>);}
children: React.ReactNode
The child components that will have access to the scroll context.
headerHeight?: number
Height of the header component in pixels.
Default: 100
tabBarHeight?: number
Height of the tab bar component in pixels.
Default: 60
useScrollToHide
Hook Primary HookThe main hook for accessing scroll-to-hide functionality and animated values.
scrollY: SharedValue
Raw scroll position value from the scroll view. Updates in real-time as user scrolls.
clampedScrollY: SharedValue
Clamped scroll position that ranges from 0 to headerHeight. Used for smooth hide/show animations.
headerTranslateY: SharedValue
Animated translateY value for the header component. Ranges from 0 to -headerHeight.
tabBarTranslateY: SharedValue
Animated translateY value for the tab bar component. Ranges from 0 to tabBarHeight.
headerHeight: number
The configured header height in pixels.
tabBarHeight: number
The configured tab bar height in pixels.
width: number
Current window width from useWindowDimensions.
onScroll: AnimatedScrollHandler
Animated scroll handler for use with Animated.ScrollView or Animated.FlatList.
onScrollRegular: ScrollHandler
Regular scroll handler for use with standard ScrollView components.
resetPosition: ResetPosition
Reset to the initial scroll position.
import { useScrollToHide } from 'rnc-theme';import Animated from 'react-native-reanimated';
function MyScreen() {const { headerTranslateY, tabBarTranslateY, onScroll, headerHeight, tabBarHeight} = useScrollToHide();
return ( <View style={{ flex: 1 }}> {/* Animated Header */} <Animated.View style={[ { position: 'absolute', top: 0, left: 0, right: 0, height: headerHeight, backgroundColor: '#fff', zIndex: 1000, }, { transform: [{ translateY: headerTranslateY }] } ]} > <Text>Header Content</Text> </Animated.View>
{/* Scrollable Content */} <Animated.ScrollView onScroll={onScroll} scrollEventThrottle={16} style={{ flex: 1, marginTop: headerHeight }} > {/* Your content here */} </Animated.ScrollView>
{/* Animated Tab Bar */} <Animated.View style={[ { position: 'absolute', bottom: 0, left: 0, right: 0, height: tabBarHeight, backgroundColor: '#f0f0f0', }, { transform: [{ translateY: tabBarTranslateY }] } ]} > <Text>Tab Bar Content</Text> </Animated.View> </View>);}
useScrollValues
Hook Lightweight HookA lightweight hook that only returns the current scroll values without the full context.
scrollValue: number
Current raw scroll position value.
clampedValue: number
Current clamped scroll position value (0 to headerHeight).
import { useScrollValues } from 'rnc-theme';
function ScrollIndicator() {const { scrollValue, clampedValue } = useScrollValues();
return ( <View> <Text>Scroll Position: {scrollValue}</Text> <Text>Clamped Value: {clampedValue}</Text> </View>);}
import { useScrollToHide } from 'rnc-theme';import Animated from 'react-native-reanimated';
function AnimatedFlatListExample() {const { onScroll, headerTranslateY } = useScrollToHide();
const data = Array.from({ length: 50 }, (_, i) => ({ id: i, title: `Item ${i}` }));
return ( <View style={{ flex: 1 }}> <Animated.View style={[ styles.header, { transform: [{ translateY: headerTranslateY }] } ]} > <Text style={styles.headerText}>Dynamic Header</Text> </Animated.View>
<Animated.FlatList data={data} onScroll={onScroll} scrollEventThrottle={16} renderItem={({ item }) => ( <View style={styles.item}> <Text>{item.title}</Text> </View> )} keyExtractor={item => item.id.toString()} /> </View>);}
import { useScrollToHide } from 'rnc-theme';import { ScrollView } from 'react-native';
function RegularScrollViewExample() {const { onScrollRegular, headerTranslateY } = useScrollToHide();
return ( <View style={{ flex: 1 }}> <Animated.View style={[ styles.header, { transform: [{ translateY: headerTranslateY }] } ]} > <Text>Header with Regular ScrollView</Text> </Animated.View>
<ScrollView onScroll={onScrollRegular} scrollEventThrottle={16} > {/* Your content */} </ScrollView> </View>);}
import { useScrollToHide } from 'rnc-theme';import { interpolate, useDerivedValue } from 'react-native-reanimated';
function CustomAnimationExample() {const { clampedScrollY, headerHeight } = useScrollToHide();
// Custom opacity animationconst headerOpacity = useDerivedValue(() => { return interpolate( clampedScrollY.value, [0, headerHeight * 0.5], [1, 0], 'clamp' );});
// Custom scale animationconst headerScale = useDerivedValue(() => { return interpolate( clampedScrollY.value, [0, headerHeight], [1, 0.8], 'clamp' );});
return ( <Animated.View style={[ styles.header, { opacity: headerOpacity, transform: [{ scale: headerScale }] } ]} > <Text>Custom Animated Header</Text> </Animated.View>);}
function CompleteLayoutExample() {const { headerTranslateY, tabBarTranslateY, onScroll, headerHeight, tabBarHeight, width} = useScrollToHide();
return ( <View style={{ flex: 1 }}> {/* Status Bar Spacer */} <View style={{ height: 44 }} />
{/* Header */} <Animated.View style={[ { position: 'absolute', top: 44, left: 0, right: 0, height: headerHeight, backgroundColor: '#ffffff', zIndex: 1000, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 5, }, { transform: [{ translateY: headerTranslateY }] } ]} > <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text style={{ fontSize: 18, fontWeight: 'bold' }}> Scroll to Hide Header </Text> </View> </Animated.View>
{/* Main Content */} <Animated.ScrollView onScroll={onScroll} scrollEventThrottle={16} style={{ flex: 1 }} contentContainerStyle={{ paddingTop: headerHeight, paddingBottom: tabBarHeight }} > {Array.from({ length: 50 }, (_, i) => ( <View key={i} style={styles.contentItem}> <Text>Content Item {i + 1}</Text> </View> ))} </Animated.ScrollView>
{/* Tab Bar */} <Animated.View style={[ { position: 'absolute', bottom: 0, left: 0, right: 0, height: tabBarHeight, backgroundColor: '#f8f8f8', flexDirection: 'row', borderTopWidth: 1, borderTopColor: '#e0e0e0', }, { transform: [{ translateY: tabBarTranslateY }] } ]} > <TouchableOpacity style={styles.tabItem}> <Text>Home</Text> </TouchableOpacity> <TouchableOpacity style={styles.tabItem}> <Text>Search</Text> </TouchableOpacity> <TouchableOpacity style={styles.tabItem}> <Text>Profile</Text> </TouchableOpacity> </Animated.View> </View>);}
// ❌ This will throw an errorfunction ComponentOutsideProvider() {const scrollContext = useScrollToHide(); // Error!return <View />;}
// Error: useScrollToHide must be used within a ScrollToHideProvider
// ✅ Correct usage within RNCProviderfunction App() {return ( <RNCProvider scrollToHideProps={{ headerHeight: 100, tabBarHeight: 60, }} > <ComponentWithScrollToHide /> </RNCProvider>);}
The components provide full TypeScript support with proper type definitions:
import { useScrollToHide, ScrollToHideProvider } from 'rnc-theme';import type { ScrollToHideProviderProps } from 'rnc-theme';
// Provider props are fully typedconst providerProps: ScrollToHideProviderProps = {children: <MyComponent />,headerHeight: 120,tabBarHeight: 80,};
function TypedComponent() {const { scrollY, // SharedValue<number> clampedScrollY, // SharedValue<number> headerTranslateY, // SharedValue<number> tabBarTranslateY, // SharedValue<number> headerHeight, // number tabBarHeight, // number width, // number onScroll, // AnimatedScrollHandler onScrollRegular, // (event: NativeSyntheticEvent<NativeScrollEvent>) => void} = useScrollToHide();
// All values are properly typed}
useScrollValues
when you only need values// The onScrollRegular handler is marked as 'worklet' for performanceconst onScrollRegular = (event: NativeSyntheticEvent<NativeScrollEvent>) => {'worklet'; // Runs on UI threadscrollY.value = event.nativeEvent.contentOffset.y;};
Consistent Heights
Define header and tab bar heights as constants to maintain consistency across your app.
Safe Area Handling
Consider device safe areas when positioning absolute elements.
Performance Testing
Test scroll performance on lower-end devices to ensure smooth animations.
const styles = StyleSheet.create({header: { position: 'absolute', top: 0, left: 0, right: 0, backgroundColor: '#ffffff', zIndex: 1000, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 5,},tabBar: { position: 'absolute', bottom: 0, left: 0, right: 0, backgroundColor: '#f8f8f8', flexDirection: 'row', borderTopWidth: 1, borderTopColor: '#e0e0e0',},contentItem: { padding: 20, borderBottomWidth: 1, borderBottomColor: '#e0e0e0',},tabItem: { flex: 1, alignItems: 'center', justifyContent: 'center',},});
Animations not smooth
Cause: Missing scrollEventThrottle={16}
or running on JS thread
Solution: Add throttling and ensure worklet usage
Header/TabBar positioning issues
Cause: Incorrect z-index or positioning values Solution: Use proper absolute positioning and z-index values
Context errors
Cause: Using hooks outside of RNCProvider
Solution: Wrap your app with RNCProvider