@ebykdrms/react-native-reanimated-modal
v0.1.1
Published
A drop-in replacement for react-native-modal powered by react-native-reanimated v4. Fully UI-thread animations, zero flickering, same familiar API.
Maintainers
Readme
@ebykdrms/react-native-reanimated-modal
A drop-in replacement for react-native-modal, powered by React Native Reanimated v4 and React Native Gesture Handler. Familiar API, zero flickering, all animations on the UI thread.
Why
react-native-modal is battle-tested but suffers from:
- Flickering on Android when JS thread is busy
- Dragging issues when
useNativeDriver={true} - Backdrop flashing workarounds required
- Animations tied to the legacy
AnimatedAPI
This library keeps the same surface-level API you already know, but runs every animation (backdrop + modal content + swipe) on the UI thread via Reanimated v4 worklets.
Installation
npm install @ebykdrms/react-native-reanimated-modal
# or
yarn add @ebykdrms/react-native-reanimated-modalPeer dependencies
npm install react-native-reanimated react-native-gesture-handlerFollow the setup guides:
Wrap your app root with GestureHandlerRootView:
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
{/* your app */}
</GestureHandlerRootView>
);
}Quick start
import { useState } from 'react';
import { Button, Text, View } from 'react-native';
import { Modal } from '@ebykdrms/react-native-reanimated-modal';
export default function Example() {
const [visible, setVisible] = useState(false);
return (
<View style={{ flex: 1 }}>
<Button title="Open" onPress={() => setVisible(true)} />
<Modal
isVisible={visible}
onBackdropPress={() => setVisible(false)}
onBackButtonPress={() => setVisible(false)}
swipeDirection={['down']}
onSwipeComplete={() => setVisible(false)}
>
<View style={{ backgroundColor: 'white', padding: 24, borderRadius: 12 }}>
<Text>Hello!</Text>
</View>
</Modal>
</View>
);
}Performance features
lazyMount (default: true)
Until the first time isVisible becomes true, the modal renders nothing — no native Modal, no backdrop, no children. After the first open it stays mounted so subsequent opens are instant.
unmountOnHide (default: false)
When true, the modal is fully unmounted from the tree after the close animation finishes. Useful for rarely-opened modals where you prefer to reclaim memory over open latency.
<Modal isVisible={visible} lazyMount unmountOnHide>
{/* ... */}
</Modal>API
All props are optional unless marked required. Props match react-native-modal where possible, so migration is usually a one-line import change.
Core
| Prop | Type | Default | Description |
|---|---|---|---|
| isVisible required | boolean | — | Whether the modal is shown |
| children required | ReactNode | — | Modal content |
| style | ViewStyle | — | Style for the content wrapper |
| testID | string | — | Test identifier for the content |
Animation
| Prop | Type | Default | Description |
|---|---|---|---|
| animationIn | AnimationValue | 'slideInUp' | Enter animation (preset name or custom) |
| animationOut | AnimationValue | 'slideOutDown' | Exit animation |
| animationInTiming | number | 300 | Enter duration in ms (ignored for spring) |
| animationOutTiming | number | 300 | Exit duration in ms (ignored for spring) |
Built-in presets: slideInUp, slideInDown, slideInLeft, slideInRight, slideOutUp, slideOutDown, slideOutLeft, slideOutRight, fadeIn, fadeOut, zoomIn, zoomOut, bounceIn, bounceOut, flipInX, flipOutX, flipInY, flipOutY.
Custom animation:
<Modal
animationIn={{
from: { translateY: 400, opacity: 0, scale: 0.9 },
to: { translateY: 0, opacity: 1, scale: 1 },
type: 'spring',
springConfig: { damping: 15, stiffness: 220, mass: 1 },
}}
animationOut={{
from: { translateY: 0, opacity: 1 },
to: { translateY: 400, opacity: 0 },
type: 'timing',
easing: 'easeIn',
}}
/>Backdrop
| Prop | Type | Default |
|---|---|---|
| hasBackdrop | boolean | true |
| backdropColor | string | 'black' |
| backdropOpacity | number | 0.7 |
| backdropTransitionInTiming | number | 300 |
| backdropTransitionOutTiming | number | 300 |
| customBackdrop | ReactNode | — |
| backdropStyle | ViewStyle | — |
| onBackdropPress | () => void | — |
Swipe to dismiss
| Prop | Type | Default |
|---|---|---|
| swipeDirection | 'up' \| 'down' \| 'left' \| 'right' \| Array<...> | — |
| swipeThreshold | number | 100 |
| panResponderThreshold | number | 4 |
| onSwipeStart | () => void | — |
| onSwipeMove | (percentageShown: number) => void | — |
| onSwipeComplete | ({ swipingDirection }) => void | — |
| onSwipeCancel | () => void | — |
| scrollOffset | number | 0 |
When swipe completes, set
isVisible={false}in your handler — the modal runs itsanimationOutand firesonModalHide.
Lifecycle
| Prop | Fires |
|---|---|
| onModalWillShow | Right before enter animation starts |
| onModalShow | After enter animation completes |
| onModalWillHide | Right before exit animation starts |
| onModalHide | After exit animation completes |
| onBackButtonPress | Android hardware back button pressed |
Layout / device
| Prop | Type | Default |
|---|---|---|
| coverScreen | boolean | true — wraps in a native Modal |
| avoidKeyboard | boolean | false |
| deviceWidth | number | auto |
| deviceHeight | number | auto |
| statusBarTranslucent | boolean | false |
Performance
| Prop | Type | Default | Description |
|---|---|---|---|
| lazyMount | boolean | true | Don't mount until first open |
| unmountOnHide | boolean | false | Unmount after close animation |
Deprecated (ignored)
These props exist for source compatibility with react-native-modal but have no effect — Reanimated always runs on the UI thread:
useNativeDriveruseNativeDriverForBackdrophideModalContentWhileAnimating
Migration from react-native-modal
In most cases:
- import Modal from 'react-native-modal';
+ import { Modal } from '@ebykdrms/react-native-reanimated-modal';Things to know:
- Animation names are a curated set (listed above); if you used an exotic
react-native-animatableanimation, pass aCustomAnimationobject instead. - The deprecated
useNativeDriver*props are silently ignored — you can delete them. - You must have
react-native-reanimatedv4 andreact-native-gesture-handlerinstalled and set up.
License
MIT © Emre Büyükdurmuş
