diff --git a/BOTTOM_SHEET_RESEARCH.md b/BOTTOM_SHEET_RESEARCH.md new file mode 100644 index 0000000..2752bf1 --- /dev/null +++ b/BOTTOM_SHEET_RESEARCH.md @@ -0,0 +1,872 @@ +# React Native Bottom Sheet Libraries - Comprehensive Research Report + +**Date:** November 16, 2025 +**Project:** Backpack Android +**Current Stack:** React Native 0.81.5, using both `@gorhom/bottom-sheet` v5.2.6 and `@lodev09/react-native-true-sheet` v2.0.6 + +--- + +## Executive Summary + +Based on extensive research of the React Native ecosystem, here are the key findings: + +1. **@gorhom/bottom-sheet** is the market leader with 779,260 weekly downloads (65% market share) +2. **@lodev09/react-native-true-sheet** has significant issues with New Architecture compatibility and Android gesture handling +3. Multiple viable alternatives exist, each with specific trade-offs +4. Custom implementation using Modal + Reanimated is feasible for full control + +--- + +## 1. Popular React Native Bottom Sheet Libraries + +### 1.1 @gorhom/bottom-sheet (RECOMMENDED) + +**Status:** Active, v5.2.6 (Latest: Sep 2025) +**Downloads:** 779,260/week +**GitHub Stars:** 8,515 +**Documentation:** https://gorhom.dev/react-native-bottom-sheet/ + +#### Pros: +- Market leader with largest community and adoption +- Built with Reanimated v3 & Gesture Handler v2 (latest technologies) +- Excellent performance using native driver animations +- Comprehensive feature set: + - Smooth gesture interactions & snapping animations + - Seamless keyboard handling for iOS & Android + - Pull-to-refresh support for scrollables + - FlatList, SectionList, ScrollView support + - React Navigation integration + - Accessibility support built-in + - Full TypeScript support + - Modal and non-modal variants + - React Native Web support + - FlashList integration + +#### Cons: +- Requires additional setup (GestureHandlerRootView wrapper) +- Larger bundle size due to feature richness +- Steeper learning curve for advanced features +- Requires peer dependencies (Reanimated v3, Gesture Handler v2) + +#### Installation: +```bash +yarn add @gorhom/bottom-sheet@^5 +yarn add react-native-reanimated react-native-gesture-handler +``` + +#### Code Example: +```tsx +import { BottomSheetModal, BottomSheetModalProvider } from '@gorhom/bottom-sheet'; +import { GestureHandlerRootView } from 'react-native-gesture-handler'; + +// Wrap app + + + {/* App content */} + + + +// Usage +const bottomSheetRef = useRef(null); +const snapPoints = useMemo(() => ['25%', '50%', '75%'], []); + + + + +``` + +#### Your Current Status: +You already have v5.2.6 installed in `/Users/wei/projects/backpack-android/android/package.json` + +--- + +### 1.2 @lodev09/react-native-true-sheet (CURRENT - PROBLEMATIC) + +**Status:** Active but buggy, v2.0.6 (Latest: 2025) +**Downloads:** Not widely tracked +**GitHub Stars:** 1,200 +**Repository:** https://github.com/lodev09/react-native-true-sheet + +#### Pros: +- True native BottomSheet implementation (iOS UISheetPresentationController, Android BottomSheetDialog) +- Lightweight +- Simple API +- Good for basic use cases + +#### Cons (CRITICAL ISSUES): +- **Android Scrolling Problems** (Most Recent): + - Issue #210: Unable to scroll up in FlatList/FlashList with RefreshControl (Nov 14, 2025) + - Issue #207: Can't scroll up inside ScrollView with scrollTo on sheet open (Sep 15, 2025) + +- **New Architecture Compatibility**: + - Issue #163: Android New Architecture no press event (Unresolved since March 2025) + - Issue #197: "View config not found for component 'TrueSheetView'" on RN 0.77.2 with newArch + - Issue #12396: Layout issues with react-navigation when New Arch enabled + +- **Performance & Rendering Issues**: + - Issue #182: List components clip when multiple components render together + - Issue #186: Footer remounts unnecessarily, degrading performance + - Issue #181: TextInput focus reliability problems on Android (Expo SDK 52, RN 0.76) + - Issue #135: Pressable items not responding inside TrueSheet on Android + +- **Build Problems**: + - Issue #164: Android build fails with Kotlin compilation errors + +- **Platform Issues**: + - iOS: Crashes on hot reload (issue #205) + - Android: Gesture handling conflicts, bitmap processing crashes + +#### Severity Assessment: +- 34+ open issues with "bug" labels +- Android platform issues dominate (7+ active Android-specific tickets) +- Library needs significant stabilization work, especially for Android + +#### Recommendation: +**AVOID for production use on Android** until Android scrolling and New Architecture issues are resolved. + +--- + +### 1.3 react-native-modal + +**Status:** Mature, 11 years old +**Downloads:** 427,603/week +**GitHub Stars:** 5,636 +**npm:** https://www.npmjs.com/package/react-native-modal + +#### Pros: +- Very stable and battle-tested +- Simple API +- Broad use case support (not just bottom sheets) +- Wide community adoption +- No complex dependencies + +#### Cons: +- Not specifically designed for bottom sheets +- Requires custom implementation for bottom sheet behavior +- Less performant than libraries using Reanimated +- Limited gesture handling out of the box + +#### Use Case: +Good for simple modal implementations where you need basic slide-up behavior without complex gestures. + +--- + +### 1.4 react-native-modalize + +**Status:** Less active maintenance +**Downloads:** 37,562/week +**GitHub Stars:** 2,891 +**npm:** https://www.npmjs.com/package/react-native-modalize + +#### Pros: +- Highly customizable +- Good documentation +- Supports snapping points, handles, and custom animations +- Works well with FlatList and ScrollView + +#### Cons: +- Declining popularity (replaced by @gorhom/bottom-sheet in most cases) +- Fewer weekly downloads than competitors +- Less active community support +- May have compatibility issues with newer React Native versions + +--- + +### 1.5 react-native-raw-bottom-sheet + +**Status:** Active, v3.0.0+ (Rewritten with Functional Components) +**Downloads:** 25,710/week +**GitHub Stars:** 1,189 +**Repository:** https://github.com/nysamnang/react-native-raw-bottom-sheet + +#### Pros: +- Lightweight (zero dependencies) +- Simple, straightforward API +- Drag-down gesture support +- Cross-platform (iOS/Android) +- Recently rewritten for better performance (v3.0.0) +- Easy to integrate + +#### Cons: +- Limited feature set compared to @gorhom/bottom-sheet +- Smaller community +- Basic animations only +- No advanced gesture handling +- Limited customization options + +#### Code Example: +```tsx +import RBSheet from 'react-native-raw-bottom-sheet'; + +const refRBSheet = useRef(); + + console.log('opened')} + onClose={() => console.log('closed')} +> + + +``` + +#### Use Case: +Good for simple bottom sheets where you don't need complex gestures or animations. + +--- + +### 1.6 reanimated-bottom-sheet (DEPRECATED) + +**Status:** No longer maintained (last update 5 years ago) +**Downloads:** 4,981/week +**GitHub Stars:** 3,346 + +**Recommendation:** DO NOT USE. Use @gorhom/bottom-sheet instead (it's the spiritual successor). + +--- + +## 2. Native Android BottomSheetDialog Implementation + +### 2.1 react-native-android-bottomsheet + +**Repository:** https://github.com/intergalacticspacehighway/react-native-android-bottomsheet +**Platform:** Android only (uses native BottomSheetDialog) + +#### Pros: +- True native Android implementation +- Excellent accessibility (works seamlessly with TalkBack) +- No custom accessibility code needed +- Native performance +- Customizable styling + +#### Cons: +- Android-only (requires separate iOS implementation) +- Requires native module bridging knowledge +- Less community support +- Limited cross-platform code reuse + +#### Features: +- Collapsible/expandable with configurable peek and max heights +- Drag-to-close and drag-to-expand gestures +- Customizable backdrop dimming +- Nested scroll views and pull-to-refresh support +- Back button dismissal + +#### Props: +```tsx + setVisible(false)} + peekHeight={300} + maxHeight={windowHeight * 0.8} + backdimAmount={0.5} + cancelable={true} + aria-label="Options menu" +> + + + + + + +``` + +#### Use Case: +Only consider if you need true native Android BottomSheetDialog behavior and can maintain separate iOS implementation. + +--- + +### 2.2 react-native-bottom-sheet-behavior (OUTDATED) + +**Repository:** https://github.com/cesardeazevedo/react-native-bottom-sheet-behavior + +**Status:** Outdated, requires manual `MainApplication.java` setup +**Recommendation:** Avoid - better alternatives exist + +--- + +## 3. Custom Implementation with React Native Modal + Reanimated + +### 3.1 Official React Native Reanimated Example + +**Documentation:** https://docs.swmansion.com/react-native-reanimated/examples/bottomsheet/ +**Last Updated:** 4 days ago (November 2025) + +#### Implementation Overview: + +```tsx +import { Modal, View, Pressable } from 'react-native'; +import Animated, { + useAnimatedStyle, + useDerivedValue, + useSharedValue, + withTiming +} from 'react-native-reanimated'; +import { Gesture, GestureDetector } from 'react-native-gesture-handler'; + +const BottomSheet = ({ isOpen, toggleSheet, duration = 500, children }) => { + const height = useSharedValue(0); + const progress = useDerivedValue(() => + withTiming(isOpen.value ? 0 : 1, { duration }) + ); + + const sheetStyle = useAnimatedStyle(() => ({ + transform: [{ translateY: progress.value * 2 * height.value }], + })); + + const backdropStyle = useAnimatedStyle(() => ({ + opacity: 1 - progress.value, + zIndex: isOpen.value ? 1 : -1, + })); + + return ( + + + + + + { + height.value = e.nativeEvent.layout.height; + }} + style={[styles.sheet, sheetStyle]} + > + {children} + + + ); +}; +``` + +#### Key Features: +- Uses `useSharedValue` for performance (runs on UI thread) +- `useDerivedValue` for interpolation logic +- `withTiming` for smooth animations +- Backdrop opacity animation +- Dynamic height measurement via `onLayout` + +--- + +### 3.2 Custom Implementation with Gesture Handler + +**Source:** Medium articles, LogRocket tutorials (November 2025) + +#### Advanced Features: +```tsx +import { PanGestureHandler } from 'react-native-gesture-handler'; + +const BottomSheetWithGestures = () => { + const translateY = useSharedValue(0); + const context = useSharedValue({ y: 0 }); + + const gesture = Gesture.Pan() + .onStart(() => { + context.value = { y: translateY.value }; + }) + .onUpdate((event) => { + translateY.value = event.translationY + context.value.y; + translateY.value = Math.max(translateY.value, 0); + }) + .onEnd((event) => { + if (event.translationY > 100) { + // Close sheet + translateY.value = withSpring(SCREEN_HEIGHT); + } else { + // Snap to position + translateY.value = withSpring(0); + } + }); + + const animatedStyle = useAnimatedStyle(() => ({ + transform: [{ translateY: translateY.value }], + })); + + return ( + + + {children} + + + ); +}; +``` + +#### Snap Points Implementation: +```tsx +const SNAP_POINTS = [SCREEN_HEIGHT * 0.1, SCREEN_HEIGHT * 0.5, SCREEN_HEIGHT * 0.9]; + +.onEnd((event) => { + const destination = snapPoint( + translateY.value, + event.velocityY, + SNAP_POINTS + ); + translateY.value = withSpring(destination, { velocity: event.velocityY }); +}); +``` + +--- + +### 3.3 Tutorial: Building from Scratch (LogRocket) + +**Source:** https://blog.logrocket.com/creating-styling-modal-bottom-sheet-react-native/ + +#### Step-by-step Implementation: + +1. **Project Setup:** +```bash +npx create-expo-app custom-bottom-sheet +npm install react-native-gesture-handler react-native-reanimated +``` + +2. **Add Reanimated Plugin to babel.config.js:** +```javascript +module.exports = { + presets: ['babel-preset-expo'], + plugins: ['react-native-reanimated/plugin'], +}; +``` + +3. **Create BottomSheet Component:** +- Use `Modal` with `transparent={true}` for overlay +- Implement gesture handling with Pan Responder or Gesture Handler +- Add backdrop with opacity animation +- Handle keyboard avoiding view for inputs + +4. **Styling Best Practices:** +```tsx +const styles = StyleSheet.create({ + modalOverlay: { + flex: 1, + justifyContent: 'flex-end', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + bottomSheet: { + backgroundColor: 'white', + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + paddingTop: 20, + shadowColor: '#000', + shadowOffset: { width: 0, height: -3 }, + shadowOpacity: 0.1, + shadowRadius: 5, + elevation: 5, + }, + dragHandle: { + width: 40, + height: 5, + backgroundColor: '#ccc', + borderRadius: 3, + alignSelf: 'center', + marginBottom: 10, + }, +}); +``` + +--- + +## 4. Performance Considerations & Best Practices + +### 4.1 General Performance Guidelines + +1. **Use Native Driver:** +```tsx +useNativeDriver: true // Always enable for better performance +``` + +2. **Optimize Re-renders:** +```tsx +// Memoize snap points +const snapPoints = useMemo(() => ['25%', '50%', '75%'], []); + +// Use shouldComponentUpdate for class components +// Or React.memo for functional components +const MemoizedContent = React.memo(BottomSheetContent); +``` + +3. **Handle Layout Changes Properly:** +```tsx +const onLayout = useCallback((event) => { + const { height } = event.nativeEvent.layout; + sheetHeight.value = height; +}, []); +``` + +4. **Minimize Shared Value Usage:** +- Only animate CSS properties that can use GPU acceleration +- Prefer `transform` and `opacity` over `width`, `height`, `backgroundColor` + +5. **Proper Cleanup:** +```tsx +useEffect(() => { + return () => { + // Clean up animations + cancelAnimation(translateY); + }; +}, []); +``` + +--- + +### 4.2 Common Performance Issues + +#### Issue: Stuttering animations +**Solution:** Extract current value at gesture start as offset +```tsx +.onStart(() => { + context.value = { y: translateY.value }; +}) +.onUpdate((event) => { + translateY.value = event.translationY + context.value.y; +}); +``` + +#### Issue: Keyboard pushing content +**Solution:** Use KeyboardAvoidingView with proper behavior +```tsx + + {children} + +``` + +#### Issue: List performance in bottom sheet +**Solution:** Use FlashList instead of FlatList +```tsx +import { FlashList } from '@shopify/flash-list'; + +// @gorhom/bottom-sheet supports FlashList integration + +``` + +--- + +### 4.3 Android-Specific Performance Issues + +1. **Gesture Conflicts:** + - Use `waitFor` and `simultaneousHandlers` to coordinate gestures + - Set proper `activeOffsetY` thresholds + +2. **Overdraw:** + - Minimize transparent overlays + - Use `removeClippedSubviews` for long lists + +3. **Memory Management:** + - Clean up refs properly + - Avoid memory leaks with proper unmounting + +--- + +## 5. Known Issues with @lodev09/react-native-true-sheet + +### Critical Issues (DO NOT USE until resolved): + +1. **Scrolling Broken on Android** (Nov 2025): + - Cannot scroll FlatList/FlashList with RefreshControl + - ScrollView scroll-to functionality breaks + - **Impact:** Major usability issue + +2. **New Architecture Incompatibility** (March 2025 - Unresolved): + - Pressable/TouchableOpacity not working on Android + - View config errors on RN 0.77.2+ + - Navigation integration broken + - **Impact:** Cannot upgrade to React Native's New Architecture + +3. **Performance Issues**: + - Unnecessary footer remounts + - List component clipping + - TextInput focus problems + - **Impact:** Poor UX, wasted renders + +4. **Build Problems**: + - Kotlin compilation errors on Android + - **Impact:** May block builds in CI/CD + +5. **Platform Instability**: + - iOS hot reload crashes + - Android bitmap processing crashes + - Safe area issues in complex layouts + - **Impact:** Development friction, production crashes + +### Assessment: +The library shows **34+ open bugs** with **7+ Android-specific issues**. It needs significant stabilization before production use, especially on Android. + +--- + +## 6. Comparison Matrix + +| Library | Weekly DL | Stars | Android | iOS | Gestures | Performance | Maintenance | New Arch | Complexity | +|---------|-----------|-------|---------|-----|----------|-------------|-------------|----------|-----------| +| @gorhom/bottom-sheet | 779k | 8.5k | Excellent | Excellent | Advanced | Excellent | Active | Yes | Medium | +| react-native-modal | 428k | 5.6k | Good | Good | Basic | Good | Active | Yes | Low | +| react-native-modalize | 38k | 2.9k | Good | Good | Good | Good | Declining | Partial | Medium | +| react-native-raw-bottom-sheet | 26k | 1.2k | Good | Good | Basic | Good | Active | Yes | Low | +| @lodev09/react-native-true-sheet | Low | 1.2k | **BROKEN** | Fair | Native | Fair | Active | **NO** | Medium | +| Custom Modal + Reanimated | N/A | N/A | Excellent | Excellent | Custom | Excellent | Self | Yes | High | +| react-native-android-bottomsheet | Low | Low | Native | N/A | Native | Excellent | Fair | Unknown | High | + +--- + +## 7. Recommendations + +### For Your Backpack Android Project: + +Given your current setup (RN 0.81.5, both @gorhom/bottom-sheet v5.2.6 and @lodev09/react-native-true-sheet v2.0.6): + +#### Immediate Action (CRITICAL): +1. **Remove @lodev09/react-native-true-sheet** due to critical Android bugs +2. **Consolidate on @gorhom/bottom-sheet v5.2.6** - you already have it installed +3. Replace all TrueSheet usages with @gorhom/bottom-sheet + +#### Migration Path: + +**From TrueSheet:** +```tsx +// OLD (TrueSheet) +import { TrueSheet } from '@lodev09/react-native-true-sheet'; + + + + + +// NEW (@gorhom/bottom-sheet) +import { BottomSheetModal } from '@gorhom/bottom-sheet'; + + + + +``` + +--- + +### General Recommendations by Use Case: + +#### Best Overall Choice: +**@gorhom/bottom-sheet** - Industry standard, best performance, most features + +#### For Simple Use Cases: +**react-native-raw-bottom-sheet** - Lightweight, zero dependencies, easy to use + +#### For Full Control: +**Custom Implementation with Modal + Reanimated** - Maximum flexibility, no dependencies + +#### Android-Only Native: +**react-native-android-bottomsheet** - True native implementation, excellent accessibility + +#### AVOID: +- **@lodev09/react-native-true-sheet** - Critical Android bugs, New Arch incompatible +- **reanimated-bottom-sheet** - Deprecated, use @gorhom instead +- **react-native-modalize** - Declining, replaced by better alternatives + +--- + +## 8. Implementation Examples + +### Example 1: Migration from TrueSheet to @gorhom + +```tsx +// 1. Update imports +import { BottomSheetModal, BottomSheetModalProvider } from '@gorhom/bottom-sheet'; + +// 2. Wrap app with provider +function App() { + return ( + + + + + + ); +} + +// 3. Update component usage +function WalletModal() { + const bottomSheetRef = useRef(null); + const snapPoints = useMemo(() => ['50%', '90%'], []); + + const handleOpen = useCallback(() => { + bottomSheetRef.current?.present(); + }, []); + + const handleClose = useCallback(() => { + bottomSheetRef.current?.dismiss(); + }, []); + + return ( + ( + + )} + > + + + + + ); +} +``` + +--- + +### Example 2: Custom Implementation (Minimal Dependencies) + +```tsx +import React, { useCallback } from 'react'; +import { Modal, View, StyleSheet, Pressable, Dimensions } from 'react-native'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withSpring, + runOnJS, +} from 'react-native-reanimated'; +import { Gesture, GestureDetector } from 'react-native-gesture-handler'; + +const { height: SCREEN_HEIGHT } = Dimensions.get('window'); + +export function CustomBottomSheet({ visible, onClose, children }) { + const translateY = useSharedValue(0); + + const gesture = Gesture.Pan() + .onUpdate((event) => { + translateY.value = Math.max(0, event.translationY); + }) + .onEnd((event) => { + if (event.translationY > 100) { + translateY.value = withSpring(SCREEN_HEIGHT, {}, () => { + runOnJS(onClose)(); + }); + } else { + translateY.value = withSpring(0); + } + }); + + const animatedStyle = useAnimatedStyle(() => ({ + transform: [{ translateY: translateY.value }], + })); + + return ( + + + + + + + + + {children} + + + + ); +} + +const styles = StyleSheet.create({ + backdrop: { + ...StyleSheet.absoluteFillObject, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + sheet: { + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + backgroundColor: 'white', + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + padding: 20, + minHeight: 200, + }, + handle: { + width: 40, + height: 5, + backgroundColor: '#ccc', + borderRadius: 3, + alignSelf: 'center', + marginBottom: 10, + }, +}); +``` + +--- + +## 9. Additional Resources + +### Official Documentation: +- @gorhom/bottom-sheet: https://gorhom.dev/react-native-bottom-sheet/ +- React Native Reanimated: https://docs.swmansion.com/react-native-reanimated/ +- React Native Gesture Handler: https://docs.swmansion.com/react-native-gesture-handler/ + +### Tutorials: +- LogRocket Custom Implementation: https://blog.logrocket.com/creating-styling-modal-bottom-sheet-react-native/ +- React Native Reanimated Bottom Sheet Example: https://docs.swmansion.com/react-native-reanimated/examples/bottomsheet/ + +### GitHub Issues to Monitor: +- TrueSheet Android Issues: https://github.com/lodev09/react-native-true-sheet/issues +- @gorhom/bottom-sheet Discussions: https://github.com/gorhom/react-native-bottom-sheet/discussions + +--- + +## 10. Conclusion + +**For your Backpack Android project:** + +1. **Immediately remove** `@lodev09/react-native-true-sheet` due to critical Android scrolling bugs and New Architecture incompatibility + +2. **Use @gorhom/bottom-sheet v5.2.6** (already installed) as your primary solution: + - Industry standard with 779k weekly downloads + - Excellent Android support + - Active maintenance and New Architecture ready + - Best performance and feature set + +3. **Consider custom implementation** only if you need: + - Very specific behavior not supported by @gorhom + - Minimal bundle size (though the savings are marginal) + - Full control over every aspect + +4. **Migration priority:** HIGH - The Android scrolling issues in TrueSheet are critical and affect core functionality + +The React Native ecosystem has clearly converged on @gorhom/bottom-sheet as the best solution for bottom sheets in 2025. Your project is already set up with the right dependency - you just need to migrate away from the problematic TrueSheet implementation. + +--- + +**Report Generated:** November 16, 2025 +**Next Steps:** +1. Review this report +2. Plan TrueSheet to @gorhom migration +3. Test thoroughly on Android with your specific use cases (especially scrolling with FlatList/RefreshControl) +4. Remove @lodev09/react-native-true-sheet from package.json after migration complete diff --git a/android/App.js b/android/App.js index 3094aee..1a138b5 100644 --- a/android/App.js +++ b/android/App.js @@ -54,6 +54,7 @@ import slip10 from "micro-key-producer/slip10.js"; import { randomBytes, secretbox } from "tweetnacl"; import bs58 from "bs58"; import { GestureHandlerRootView } from "react-native-gesture-handler"; +import { SafeAreaProvider } from 'react-native-safe-area-context'; // Replaced @gorhom/bottom-sheet with simple Modal-based implementation import BottomSheet, { SimpleBottomSheetView as BottomSheetView, @@ -78,8 +79,8 @@ import { BiometricSettings } from "./src/auth/BiometricSettings"; // Import native USB Ledger module const { LedgerUsb } = NativeModules; -// Import TrueSheet -import { TrueSheet } from "@lodev09/react-native-true-sheet"; +// Import SimpleActionSheet as TrueSheet for easy migration +import TrueSheet from "./components/SimpleActionSheet"; // Import screens import SendScreen from './screens/SendScreen'; @@ -7063,8 +7064,10 @@ const styles = StyleSheet.create({ // Export App with ApolloProvider wrapper export default function App() { return ( - - - + + + + + ); } diff --git a/android/BOTTOM_SHEET_RESEARCH.md b/android/BOTTOM_SHEET_RESEARCH.md new file mode 100644 index 0000000..9b84e21 --- /dev/null +++ b/android/BOTTOM_SHEET_RESEARCH.md @@ -0,0 +1,1024 @@ +# React Native Bottom Sheet Implementation Research +## Custom Solutions WITHOUT Reanimated + +This document provides comprehensive research on implementing bottom sheets in React Native without using Reanimated or complex animation libraries, focusing on Android compatibility and performance. + +--- + +## Table of Contents +1. [Custom Implementation with PanResponder](#1-custom-implementation-with-panresponder) +2. [Custom Implementation with Animated API](#2-custom-implementation-with-animated-api) +3. [Lightweight Library Alternatives](#3-lightweight-library-alternatives) +4. [Native Android BottomSheetDialog](#4-native-android-bottomsheetdialog) +5. [Performance & Compatibility Comparison](#5-performance--compatibility-comparison) +6. [Recommendations](#6-recommendations) + +--- + +## 1. Custom Implementation with PanResponder + +### Overview +Using React Native's built-in `Modal` component with `PanResponder` for gesture handling provides full control without external dependencies. + +### Complete Implementation Example + +Based on [this gist](https://gist.github.com/mizanxali/df7bc82a1dadf3723c15603cd385d53b), here's a production-ready implementation: + +```typescript +import React, { useRef, useEffect } from 'react'; +import { + View, + Modal, + Animated, + PanResponder, + Dimensions, + StyleSheet, + TouchableOpacity, +} from 'react-native'; + +interface BottomSheetProps { + visible: boolean; + onClose: () => void; + children: React.ReactNode; + closeOnSwipeDown?: boolean; + height?: number; +} + +const BottomSheet: React.FC = ({ + visible, + onClose, + children, + closeOnSwipeDown = true, + height = 300, +}) => { + const screenHeight = Dimensions.get('window').height; + const translateY = useRef(new Animated.Value(0)).current; + + const panResponder = useRef( + PanResponder.create({ + onStartShouldSetPanResponder: () => closeOnSwipeDown, + onPanResponderMove: (_, gestureState) => { + // Only allow downward swipes + if (gestureState.dy > 0) { + translateY.setValue(gestureState.dy); + } + }, + onPanResponderRelease: (_, gestureState) => { + const shouldClose = + gestureState.dy > height * 0.4 || gestureState.vy > 0.5; + + if (shouldClose) { + closeSheet(); + } else { + // Snap back to original position + Animated.spring(translateY, { + toValue: 0, + useNativeDriver: true, + }).start(); + } + }, + }) + ).current; + + const openSheet = () => { + Animated.timing(translateY, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start(); + }; + + const closeSheet = () => { + Animated.timing(translateY, { + toValue: screenHeight, + duration: 200, + useNativeDriver: true, + }).start(() => { + onClose(); + }); + }; + + useEffect(() => { + if (visible) { + openSheet(); + } + }, [visible]); + + return ( + + + + + {children} + + + + ); +}; + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + justifyContent: 'flex-end', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + sheet: { + backgroundColor: 'white', + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + padding: 16, + }, +}); + +export default BottomSheet; +``` + +### Key Implementation Details + +**PanResponder Gesture Handling:** +- `onStartShouldSetPanResponder`: Activates when `closeOnSwipeDown` is enabled +- `onPanResponderMove`: Tracks downward gesture movement (dy > 0) and updates animated value +- `onPanResponderRelease`: Closes sheet if swiped past 40% of height OR velocity exceeds 0.5 + +**Animation Strategy:** +- Uses `Animated.timing()` for open/close transitions +- Uses `Animated.spring()` to snap back when gesture doesn't meet close threshold +- `useNativeDriver: true` enables native thread animations for 60fps performance + +**Common Pitfalls to Avoid:** +1. **Don't intercept button presses**: Set `onMoveShouldSetPanResponder` to false to prevent gesture handler from blocking child component interactions +2. **Prevent stuttering**: Extract current translateY value in `onPanResponderGrant`, use as offset, and reset to 0 +3. **Android back button**: Handle `onRequestClose` prop on Modal + +### Tutorial Resources +- [Medium: BottomSheet with PanResponder](https://andriidrozdov.medium.com/bottomsheet-with-reactnative-receipt-of-duck-soup-e3ded07f2f49) +- [Medium: Bottom Sheet with React Native](https://arbaz5256.medium.com/bottom-sheet-with-react-native-c249130bed63) +- [GitHub Example](https://github.com/ruslanzharkov/react-native-bottom-sheet) + +--- + +## 2. Custom Implementation with Animated API + +### Overview +Using React Native's `Animated` API without PanResponder - simpler but less interactive. + +### Complete Implementation + +Based on [CodeDaily tutorial](https://www.codedaily.io/tutorials/Create-a-Custom-Animated-Bottom-Action-Sheet-without-Measuring-in-React-Native): + +```javascript +import React, { useState } from 'react'; +import { + View, + Modal, + Animated, + Dimensions, + StyleSheet, + TouchableWithoutFeedback, +} from 'react-native'; + +const { height: screenHeight } = Dimensions.get('window'); + +const BottomActionSheet = ({ visible, onClose, children }) => { + const [animation] = useState(new Animated.Value(0)); + + const handleOpen = () => { + Animated.timing(animation, { + toValue: 1, + duration: 300, + useNativeDriver: true, + }).start(); + }; + + const handleClose = () => { + Animated.timing(animation, { + toValue: 0, + duration: 200, + useNativeDriver: true, + }).start(() => { + onClose(); + }); + }; + + React.useEffect(() => { + if (visible) { + handleOpen(); + } + }, [visible]); + + // Backdrop opacity animation with "cliff" technique + const backdropOpacity = animation.interpolate({ + inputRange: [0, 0.01, 1], + outputRange: [0, 0, 0.5], + }); + + // Sheet translation animation + const translateY = animation.interpolate({ + inputRange: [0, 1], + outputRange: [0, -screenHeight], + }); + + return ( + + + + + + + {children} + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + backdrop: { + ...StyleSheet.absoluteFillObject, + backgroundColor: 'black', + }, + popup: { + position: 'absolute', + top: screenHeight, + left: 0, + right: 0, + height: '100%', + justifyContent: 'flex-end', + }, +}); + +export default BottomActionSheet; +``` + +### Key Techniques + +**No Measurement Required:** +- Uses absolute positioning: `top: screenHeight` to place sheet off-screen initially +- Uses `justifyContent: 'flex-end'` to render content at bottom regardless of size +- Translation of `-screenHeight` when animation value reaches 1 + +**Backdrop "Cliff" Interpolation:** +- Interpolation from 0 to 0.01 creates instant positioning +- Then fades opacity from 0.01 to 0.5 +- Prevents visual glitches as overlay moves into place + +**Advantages:** +- Simpler than PanResponder - no complex gesture logic +- "100% reversible" animations driven by single animated value +- Avoids measuring inner content dimensions + +**Limitations:** +- No swipe-to-dismiss gesture +- Less interactive than PanResponder solution + +--- + +## 3. Lightweight Library Alternatives + +### 3.1 react-native-raw-bottom-sheet + +**Best for:** Zero-dependency, simple bottom sheets + +#### Installation +```bash +npm i react-native-raw-bottom-sheet --save +# or +yarn add react-native-raw-bottom-sheet +``` + +#### Implementation +```javascript +import React, { useRef } from 'react'; +import { View, Button } from 'react-native'; +import RBSheet from 'react-native-raw-bottom-sheet'; + +export default function Example() { + const refRBSheet = useRef(); + + return ( + +