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