1 | 🚧 We're looking for maintainers and contributors! See [#414](https://github.com/react-native-community/react-native-modal/issues/414)
|
2 |
|
3 | <br />
|
4 |
|
5 | # react-native-modal
|
6 |
|
7 | [![npm version](https://badge.fury.io/js/react-native-modal.svg)](https://badge.fury.io/js/react-native-modal)
|
8 | [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
|
9 |
|
10 | An enhanced, animated and customizable react-native modal.
|
11 |
|
12 | The goal of `react-native-modal` is expanding the original react-native `Modal` component by adding animations and styles customization options while still providing a plain-simple API.
|
13 |
|
14 | <p align="center">
|
15 | <img src="/.github/images/example-modal.gif" height="500" />
|
16 | </p>
|
17 |
|
18 | ## Features
|
19 |
|
20 | - Smooth enter/exit animations
|
21 | - Plain simple and flexible APIs
|
22 | - Customizable backdrop opacity, color and timing
|
23 | - Listeners for the modal animations ending
|
24 | - Resize itself correctly on device rotation
|
25 | - Swipeable
|
26 | - Scrollable
|
27 |
|
28 | ## Setup
|
29 |
|
30 | This library is available on npm, install it with: `npm i react-native-modal` or `yarn add react-native-modal`.
|
31 |
|
32 | ## Usage
|
33 |
|
34 | Since react-native-modal is an extension of the original react native modal, it works in a similar fashion [react-native original modal](https://reactnative.dev/docs/modal.html).
|
35 |
|
36 | 1. Import react-native-modal:
|
37 |
|
38 | ```javascript
|
39 | import Modal from 'react-native-modal';
|
40 | ```
|
41 |
|
42 | 2. Create a modal and nest its content inside of it:
|
43 |
|
44 | ```javascript
|
45 | function WrapperComponent() {
|
46 | return (
|
47 | <View>
|
48 | <Modal>
|
49 | <View style={{flex: 1}}>
|
50 | <Text>I am the modal content!</Text>
|
51 | </View>
|
52 | </Modal>
|
53 | </View>
|
54 | );
|
55 | }
|
56 | ```
|
57 |
|
58 | 3. Then simply show it by setting the `isVisible` prop to true:
|
59 |
|
60 | ```javascript
|
61 | function WrapperComponent() {
|
62 | return (
|
63 | <View>
|
64 | <Modal isVisible={true}>
|
65 | <View style={{flex: 1}}>
|
66 | <Text>I am the modal content!</Text>
|
67 | </View>
|
68 | </Modal>
|
69 | </View>
|
70 | );
|
71 | }
|
72 | ```
|
73 |
|
74 | The `isVisible` prop is the only prop you'll really need to make the modal work: you should control this prop value by saving it in your wrapper component state and setting it to `true` or `false` when needed.
|
75 |
|
76 | ## A complete example
|
77 |
|
78 | The following example consists in a component (`ModalTester`) with a button and a modal.
|
79 | The modal is controlled by the `isModalVisible` state variable and it is initially hidden, since its value is `false`.
|
80 | Pressing the button sets `isModalVisible` to true, making the modal visible.
|
81 | Inside the modal there is another button that, when pressed, sets `isModalVisible` to false, hiding the modal.
|
82 |
|
83 | ```javascript
|
84 | import React, {useState} from 'react';
|
85 | import {Button, Text, View} from 'react-native';
|
86 | import Modal from 'react-native-modal';
|
87 |
|
88 | function ModalTester() {
|
89 | const [isModalVisible, setModalVisible] = useState(false);
|
90 |
|
91 | const toggleModal = () => {
|
92 | setModalVisible(!isModalVisible);
|
93 | };
|
94 |
|
95 | return (
|
96 | <View style={{flex: 1}}>
|
97 | <Button title="Show modal" onPress={toggleModal} />
|
98 |
|
99 | <Modal isVisible={isModalVisible}>
|
100 | <View style={{flex: 1}}>
|
101 | <Text>Hello!</Text>
|
102 |
|
103 | <Button title="Hide modal" onPress={toggleModal} />
|
104 | </View>
|
105 | </Modal>
|
106 | </View>
|
107 | );
|
108 | }
|
109 |
|
110 | export default ModalTester;
|
111 | ```
|
112 |
|
113 | For a more complex example take a look at the `/example` directory.
|
114 |
|
115 | ## Available props
|
116 |
|
117 | | Name | Type | Default | Description |
|
118 | | ------------------------------ | ---------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
119 | | animationIn | string or object | 'slideInUp' | Modal show animation |
|
120 | | animationInTiming | number | 300 | Timing for the modal show animation (in ms) |
|
121 | | animationOut | string or object | 'slideOutDown' | Modal hide animation |
|
122 | | animationOutTiming | number | 300 | Timing for the modal hide animation (in ms) |
|
123 | | avoidKeyboard | bool | false | Move the modal up if the keyboard is open |
|
124 | | coverScreen | bool | true | Will use RN `Modal` component to cover the entire screen wherever the modal is mounted in the component hierarchy |
|
125 | | hasBackdrop | bool | true | Render the backdrop |
|
126 | | backdropColor | string | 'black' | The backdrop background color |
|
127 | | backdropOpacity | number | 0.70 | The backdrop opacity when the modal is visible |
|
128 | | backdropTransitionInTiming | number | 300 | The backdrop show timing (in ms) |
|
129 | | backdropTransitionOutTiming | number | 300 | The backdrop hide timing (in ms) |
|
130 | | customBackdrop | node | null | The custom backdrop element |
|
131 | | children | node | **REQUIRED** | The modal content |
|
132 | | deviceHeight | number | null | Device height (useful on devices that can hide the navigation bar) |
|
133 | | deviceWidth | number | null | Device width (useful on devices that can hide the navigation bar) |
|
134 | | isVisible | bool | **REQUIRED** | Show the modal? |
|
135 | | onBackButtonPress | func | () => null | Called when the Android back button is pressed |
|
136 | | onBackdropPress | func | () => null | Called when the backdrop is pressed |
|
137 | | onModalWillHide | func | () => null | Called before the modal hide animation begins |
|
138 | | onModalHide | func | () => null | Called when the modal is completely hidden |
|
139 | | onModalWillShow | func | () => null | Called before the modal show animation begins |
|
140 | | onModalShow | func | () => null | Called when the modal is completely visible |
|
141 | | onSwipeStart | func | () => null | Called when the swipe action started |
|
142 | | onSwipeMove | func | (percentageShown) => null | Called on each swipe event |
|
143 | | onSwipeComplete | func | ({ swipingDirection }) => null | Called when the `swipeThreshold` has been reached |
|
144 | | onSwipeCancel | func | () => null | Called when the `swipeThreshold` has not been reached |
|
145 | | panResponderThreshold | number | 4 | The threshold for when the panResponder should pick up swipe events |
|
146 | | scrollOffset | number | 0 | When > 0, disables swipe-to-close, in order to implement scrollable content |
|
147 | | scrollOffsetMax | number | 0 | Used to implement overscroll feel when content is scrollable. See `/example` directory |
|
148 | | scrollTo | func | null | Used to implement scrollable modal. See `/example` directory for reference on how to use it |
|
149 | | scrollHorizontal | bool | false | Set to true if your scrollView is horizontal (for a correct scroll handling) |
|
150 | | swipeThreshold | number | 100 | Swiping threshold that when reached calls `onSwipeComplete` |
|
151 | | swipeDirection | string or array | null | Defines the direction where the modal can be swiped. Can be 'up', 'down', 'left, or 'right', or a combination of them like `['up','down']` |
|
152 | | useNativeDriver | bool | false | Defines if animations should use native driver |
|
153 | | useNativeDriverForBackdrop | bool | null | Defines if animations for backdrop should use native driver (to avoid flashing on android) |
|
154 | | hideModalContentWhileAnimating | bool | false | Enhances the performance by hiding the modal content until the animations complete |
|
155 | | propagateSwipe | bool or func | false | Allows swipe events to propagate to children components (eg a ScrollView inside a modal) |
|
156 | | style | any | null | Style applied to the modal |
|
157 |
|
158 | ## Frequently Asked Questions
|
159 |
|
160 | ### The component is not working as expected
|
161 |
|
162 | Under the hood `react-native-modal` uses react-native original [Modal component](https://reactnative.dev/docs/modal.html).
|
163 | Before reporting a bug, try swapping `react-native-modal` with react-native original Modal component and, if the issue persists, check if it has already been reported as a [react-native issue](https://github.com/facebook/react-native/issues).
|
164 |
|
165 | ### The backdrop is not completely filled/covered on some Android devices (Galaxy, for one)
|
166 |
|
167 | React-Native has a few issues detecting the correct device width/height of some devices.
|
168 | If you're experiencing this issue, you'll need to install [`react-native-extra-dimensions-android`](https://github.com/Sunhat/react-native-extra-dimensions-android).
|
169 | Then, provide the real window height (obtained from `react-native-extra-dimensions-android`) to the modal:
|
170 |
|
171 | ```javascript
|
172 | const deviceWidth = Dimensions.get('window').width;
|
173 | const deviceHeight =
|
174 | Platform.OS === 'ios'
|
175 | ? Dimensions.get('window').height
|
176 | : require('react-native-extra-dimensions-android').get(
|
177 | 'REAL_WINDOW_HEIGHT',
|
178 | );
|
179 |
|
180 | function WrapperComponent() {
|
181 | const [isModalVisible, setModalVisible] = useState(true);
|
182 |
|
183 | return (
|
184 | <Modal
|
185 | isVisible={isModalVisible}
|
186 | deviceWidth={deviceWidth}
|
187 | deviceHeight={deviceHeight}>
|
188 | <View style={{flex: 1}}>
|
189 | <Text>I am the modal content!</Text>
|
190 | </View>
|
191 | </Modal>
|
192 | );
|
193 | }
|
194 | ```
|
195 |
|
196 | ### How can I hide the modal by pressing outside of its content?
|
197 |
|
198 | The prop `onBackdropPress` allows you to handle this situation:
|
199 |
|
200 | ```javascript
|
201 | <Modal
|
202 | isVisible={isModalVisible}
|
203 | onBackdropPress={() => setModalVisible(false)}>
|
204 | <View style={{flex: 1}}>
|
205 | <Text>I am the modal content!</Text>
|
206 | </View>
|
207 | </Modal>
|
208 | ```
|
209 |
|
210 | ### How can I hide the modal by swiping it?
|
211 |
|
212 | The prop `onSwipeComplete` allows you to handle this situation (remember to set `swipeDirection` too!):
|
213 |
|
214 | ```javascript
|
215 | <Modal
|
216 | isVisible={isModalVisible}
|
217 | onSwipeComplete={() => setModalVisible(false)}
|
218 | swipeDirection="left">
|
219 | <View style={{flex: 1}}>
|
220 | <Text>I am the modal content!</Text>
|
221 | </View>
|
222 | </Modal>
|
223 | ```
|
224 |
|
225 | Note that when using `useNativeDriver={true}` the modal won't drag correctly. This is a [known issue](https://github.com/react-native-community/react-native-modal/issues/163#issuecomment-409760695).
|
226 |
|
227 | ### The modal flashes in a weird way when animating
|
228 |
|
229 | Unfortunately this is a [known issue](https://github.com/react-native-community/react-native-modal/issues/92) that happens when `useNativeDriver=true` and must still be solved.
|
230 | In the meanwhile as a workaround you can set the `hideModalContentWhileAnimating` prop to `true`: this seems to solve the issue.
|
231 | Also, do not assign a `backgroundColor` property directly to the Modal. Prefer to set it on the child container.
|
232 |
|
233 | ### The modal background doesn't animate properly
|
234 |
|
235 | Are you sure you named the `isVisible` prop correctly? Make sure it is spelled correctly: `isVisible`, not `visible`.
|
236 |
|
237 | ### The modal doesn't change orientation
|
238 |
|
239 | Add a `supportedOrientations={['portrait', 'landscape']}` prop to the component, as described [in the React Native documentation](https://reactnative.dev/docs/modal.html#supportedorientations).
|
240 |
|
241 | Also, if you're providing the `deviceHeight` and `deviceWidth` props you'll have to manually update them when the layout changes.
|
242 |
|
243 | ### I can't show multiple modals one after another
|
244 |
|
245 | Unfortunately right now react-native doesn't allow multiple modals to be displayed at the same time.
|
246 | This means that, in `react-native-modal`, if you want to immediately show a new modal after closing one you must first make sure that the modal that your closing has completed its hiding animation by using the `onModalHide` prop.
|
247 |
|
248 | ### I can't show multiple modals at the same time
|
249 |
|
250 | See the question above.
|
251 | Showing multiple modals (or even alerts/dialogs) at the same time is not doable because of a react-native bug.
|
252 | That said, I would strongly advice against using multiple modals at the same time because, most often than not, this leads to a bad UX, especially on mobile (just my opinion).
|
253 |
|
254 | ### The StatusBar style changes when the modal shows up
|
255 |
|
256 | This issue has been discussed [here](https://github.com/react-native-community/react-native-modal/issues/50).
|
257 | The TLDR is: it's a know React-Native issue with the Modal component 😞
|
258 |
|
259 | ### The modal is not covering the entire screen
|
260 |
|
261 | The modal style applied by default has a small margin.
|
262 | If you want the modal to cover the entire screen you can easily override it this way:
|
263 |
|
264 | ```js
|
265 | <Modal style={{margin: 0}}>...</Modal>
|
266 | ```
|
267 |
|
268 | ### I can't scroll my ScrollView inside of the modal
|
269 |
|
270 | Enable propagateSwipe to allow your child components to receive swipe events:
|
271 |
|
272 | ```js
|
273 | <Modal propagateSwipe>...</Modal>
|
274 | ```
|
275 |
|
276 | Please notice that this is still a WIP fix and might not fix your issue yet, see [issue #236](https://github.com/react-native-community/react-native-modal/issues/236).
|
277 |
|
278 | ### The modal enter/exit animation flickers
|
279 |
|
280 | Make sure your `animationIn` and `animationOut` are set correctly.
|
281 | We noticed that, for example, using `fadeIn` as an exit animation makes the modal flicker (it should be `fadeOut`!).
|
282 | Also, some users have noticed that setting backdropTransitionOutTiming={0} can fix the flicker without affecting the animation.
|
283 |
|
284 | ### The custom backdrop doesn't fill the entire screen
|
285 |
|
286 | You need to specify the size of your custom backdrop component. You can also make it expand to fill the entire screen by adding a `flex: 1` to its style:
|
287 |
|
288 | ```javascript
|
289 | <Modal isVisible={isModalVisible} customBackdrop={<View style={{flex: 1}} />}>
|
290 | <View style={{flex: 1}}>
|
291 | <Text>I am the modal content!</Text>
|
292 | </View>
|
293 | </Modal>
|
294 | ```
|
295 |
|
296 | ### The custom backdrop doesn't dismiss the modal on press
|
297 |
|
298 | You can provide an event handler to the custom backdrop element to dismiss the modal. The prop `onBackdropPress` is not supported for a custom backdrop.
|
299 |
|
300 | ```javascript
|
301 | <Modal
|
302 | isVisible={isModalVisible}
|
303 | customBackdrop={
|
304 | <TouchableWithoutFeedback onPress={dismissModalHandler}>
|
305 | <View style={{flex: 1}} />
|
306 | </TouchableWithoutFeedback>
|
307 | }
|
308 | />
|
309 | ```
|
310 |
|
311 | ## Available animations
|
312 |
|
313 | Take a look at [react-native-animatable](https://github.com/oblador/react-native-animatable) to see the dozens of animations available out-of-the-box. You can also pass in custom animation definitions and have them automatically register with react-native-animatable. For more information on creating custom animations, see the react-native-animatable [animation definition schema](https://github.com/oblador/react-native-animatable#animation-definition-schema).
|
314 |
|
315 | ## Acknowledgements
|
316 |
|
317 | Thanks [@oblador](https://github.com/oblador) for react-native-animatable, [@brentvatne](https://github.com/brentvatne) for the npm namespace and to anyone who contributed to this library!
|
318 |
|
319 | Pull requests, feedbacks and suggestions are welcome!
|