Close react native modal by clicking on overlay?

JavascriptReact Native

Javascript Problem Overview


Is it possible to close react native modal by clicking on overlay when transparent option is true? Documentation doesn't provide anything about it. Is it possible?

Javascript Solutions


Solution 1 - Javascript

If I understood correctly, you want to close the modal when the user clicks outside of it, right ?

If yes, I searched for this some time ago and the only solution that I remember was this one (which is the one that I've been using so far):

render() { 
  if (!this.state.modalVisible)
	return null
  return (
     <View>
		<Modal 
		  animationType="fade"
	      transparent={true}
		  visible={this.state.modalVisible}
		  onRequestClose={() => {this.setModalVisible(false)}}
	    >
		  <TouchableOpacity 
		    style={styles.container} 
		    activeOpacity={1} 
			onPressOut={() => {this.setModalVisible(false)}}
          >
	        <ScrollView 
			  directionalLockEnabled={true} 
			  contentContainerStyle={styles.scrollModal}
            >
			  <TouchableWithoutFeedback>
			    <View style={styles.modalContainer}>
				  // Here you put the content of your modal.
				</View>
			  </TouchableWithoutFeedback>
			</ScrollView>
		  </TouchableOpacity>	
		</Modal> 
     </View>
  )
} 

// Then on setModalVisible(), you do everything that you need to do when closing or opening the modal.
setModalVisible(visible) {
	this.setState({
		modalVisible: visible,
	})
}

Explanation

This is basically using a TouchableOpacity in the whole screen to get when the user clicks to close the modal. The TouchableWithoutFeedback is to avoid the TouchableOpacity to work inside of the Modal.

If you have a better solution, please share here.

Solution 2 - Javascript

Another solution:

// Modal.js
import React from 'react';
import {
  TouchableWithoutFeedback,
  StyleSheet,
  Modal,
  View,
} from 'react-native';
import t from 'prop-types';


class MyModal extends React.Component {
  static propTypes = {
    children: t.node.isRequired,
    visible: t.bool.isRequired,
    dismiss: t.func.isRequired,
    transparent: t.bool,
    animationType: t.string,
  };

  static defaultProps = {
    animationType: 'none',
    transparent: true,
  };

  render() {
    const { props } = this;
    return (
      <View>
        <Modal
          visible={props.visible}
          transparent={props.transparent}
          onRequestClose={props.dismiss}
          animationType={props.animationType}
        >
          <TouchableWithoutFeedback onPress={props.dismiss}>
            <View style={styles.modalOverlay} />
          </TouchableWithoutFeedback>

          <View style={styles.modalContent}>
            {props.children}
          </View>
        </Modal>
      </View>
    );
  }
}


const styles = StyleSheet.create({
  modalContent: {
    flex: 1,
    justifyContent: 'center',
    margin: '5%',
  },
  modalOverlay: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: 'rgba(0,0,0,0.5)'
  },
});


export default MyModal;

Usage example:

// SomeScreen.js
import React from 'react';
import { View, Text, Button } from 'react-native';

import Modal from './Modal';


class SomeScreen extends React.Component {
  state = {
    isModalVisible: false,
  };

  showModal = () => this.setState({ isModalVisible: true });
  hideModal = () => this.setState({ isModalVisible: false });

  render() {
    return (
      <View>
        <Button onPress={this.showModal} />
        <Modal
          visible={this.state.isModalVisible}
          dismiss={this.hideModal}
        >
          <Text>Hello, I am modal</Text>
        </Modal>
      </View>
    );
  }
}

Solution 3 - Javascript

Simple solution. You need one touchableOpacity for clicking outside and another touchableOpacity for actual modal that will do nothing onPress.

import React, { Component } from 'react'
import { View, StyleSheet, TouchableOpacity, Modal} from 'react-native';

export class Modal extends Component {
    constructor(props){
        this.state = { modalVisible : true}
    }
    render() {
        return (
            <View>
                <Modal
                    animationType="slide"
                    transparent={true}
                    visible={this.state.modalVisible}
                    onRequestClose={() => { this.setState({modalVisible: false})
                    }}
                  >
                    <TouchableOpacity style={styles.modalContainer} onPress={() => { this.setState({ modalVisible : false})}}>
                        <TouchableOpacity style={styles.modal} onPress={() => console.log('do nothing')} activeOpacity={1} >
                            Modal Content...
                        </TouchableOpacity>
                    </TouchableOpacity>
                </Modal>
            </View>
        )
    }
}


const styles = StyleSheet.create({
    modalContainer: {
      flex: 1,
      justifyContent: 'center',
      alignItems: 'center',
    },
    modal: {
      width: 155,
      height: 300
    },
});

export default Modal;

activeOpacity={1} just removes touchableOpacity fade effect

Solution 4 - Javascript

We can work it out by adding:

import { TouchableOpacity } from 'react-native';
    <TouchableOpacity onPress={()=>this.setState({modalVisibilty:false})}>
    <View style={{opacity:0, flex:1 }}/>
    </TouchableOpacity>

under the window and another one above and change the layout style to fit your screen.

Explanation:

You will make 2 big hidden buttons to catch the user touch and change the modal visibility state to false.

Solution 5 - Javascript

Here is my simple implementation:

<TouchableWithoutFeedback onPress={}> // Code to close your modal goes here
	<View style={styles.background}> // The view to drawn the background
			<View
				onStartShouldSetResponder={() => true}
				style={styles.container}
			> // The view to drawn your modal
			// Your content
			</View>
		</Androw>
	</View>
</TouchableWithoutFeedback>

I use TouchableWithoutFeedback since i do not want to change the background color when clicking on it. I also added onStartShouldSetResponder on the modal view to prevent closing the modal when you click inside the view.

I am also not using the Modal component because i done it using react-navigation.

Solution 6 - Javascript

<Modal isVisible={this.state.isVisible}
onBackdropPress={() => this.setState({ isVisible: false })}>
<View style={{ flex: 1 }}>
<Text>I am the modal content!</Text>
</View>
</Modal>

Solution 7 - Javascript

        <Modal
            animationType="slide"
            closeOnClick={true}
            transparent={true}
            visible={this.state.modalVisible}
            >
            <TouchableOpacity onPress={() => { this.setModalVisible(!this.state.modalVisible)}} style={{flex:1, justifyContent:'center', alignItems:'center',}}>
                <View style={{flex:0.2,backgroundColor:'white', margin:20, borderRadius:20, borderWidth:2, borderColor:'gray'}}>
                    <Text style={{margin:20}}>모달 테스트</Text>
                </View>
            </TouchableOpacity>
        </Modal>

this code is my solution.

Solution 8 - Javascript

Simple solution with simple code, if you are using expo.
Here is a complete component which can you just copy and paste and get it working.

//MyModal.js

import React from 'react';
import { BlurView } from 'expo-blur';
import { View, StyleSheet, Modal, TouchableWithoutFeedback } from 'react-native';

export const MyModal = ({ children, visible, onRequestClose, onPressOverlay, }) => {
  return (
     <Modal
      visible={visible}
      transparent
      animationType='none'
      onRequestClose={onRequestClose}
     >
       <TouchableWithoutFeedback onPress={onPressOverlay}>
         <BlurView
           style={{ ...StyleSheet.absoluteFill }}
           tint='dark'
           intensity={100}
         />
       </TouchableWithoutFeedback>
       <View style={styles.container}>
         {children}
       </View>
    </Modal>
  );
};

const styles = StyleSheet.create({
  container: {
    height: '100%',
    width: '100%',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Now you can import it into your work-space and use it like this.
I'm using functional component and useState hook to show or hide the modal.

//yourScreen.js

import React, { useState } from 'react';
import { View, Button } from 'react-native';
import { MyModal } form './path/to/MyModal.js';

const YourScreen = () => {
  const [modalVisible, setModalVisible] = useState(false);
  return (
    <View style={{ flex:1 }}>
      <MyModal
        visible={modalVisible}
        onRequestClose={()=>{
          setModalVisible(false);
        }}
        onPressOverlay={()=>{
          setModalVisible(!modalVisible);
        }}
      >
        // your modal content goes here
      </MyModal>
      <Button
        title='Show Modal' 
        onPress={()=>{
          setModalVisible(!modalVisible);
        }}
      />
    </View>
  );
}

export default YourScreen;

Solution 9 - Javascript

Here is my solution,

import React from "react";
import {Modal, StyleSheet, TouchableOpacity, TouchableWithoutFeedback, View} from "react-native";

// make sure the styles props is passed to the model and contains modalContainer, as well as the childrenContainer style objects.

export default class DismissibleModal extends React.Component {
    render() {
        return (
            <Modal
              animationType="slide"
              transparent={true}
              visible={this.props.visible}
              onRequestClose={() => {this.props.dismiss()}}>
              <TouchableOpacity
                style={Styles.modal}
                activeOpacity={1}
                onPressOut={() => {this.props.dismiss()}}>
                    <View style={[this.props.styles.modalContainer]}>
                        <TouchableWithoutFeedback>
                            <View style={[this.props.styles.childrenContainer]}>
                                {this.props.children}
                            </View>
                        </TouchableWithoutFeedback>
                    </View>
              </TouchableOpacity>
        </Modal>
        )
    }

}



const Styles = StyleSheet.create({
    modal: {
        flex: 1,
        backgroundColor: 'transparent',
        flexDirection: 'row',
        justifyContent: 'center',
        alignItems: 'center'
    },
    modalView: {
        backgroundColor: "white",
        borderRadius: 10,
        padding: 20,
        paddingTop: 20,
        alignItems: "center",
        shadowColor: "#000",
        shadowOffset: {
            width: 0,
            height: 9,
        },
        shadowOpacity: 0.50,
        shadowRadius: 12.35,

        elevation: 19,
  },
});

Solution 10 - Javascript

@Gui Herzog answer are quite good, but makingTouchableOpacity as a parent component, it deals with conflict when it comes touching child component.

it's not good practice having a multiple component inside of TouchableOpacity, else all component inside TouchableOpacity parent will be clickable, best way to address this problem is, make your TouchableOpacity component in absolute position, with 100% width and height in screen.

here's some example: Modal.js

export default function(props){
    const { width, height } = Dimensions.get('window');
    const hp = hp => (hp / 100) * height;
    const wp = wp => (wp / 100) * width;
    const size = size => Math.round((size / 100) * width);
    return (
        <KeyboardAvoidingView>
            <TouchableOpacity
                style={{ position: 'absolute', width: wp(100), height: hp(100) }}
                onPress={props.onTouchOutSide}
            />
            <ScrollView>
                {/*...child*/}
            </ScrollView>
        </KeyboardAvoidingView>
    )
}

You Welcome.

Solution 11 - Javascript

Here is the best solution for you just copy and paste. It will work definitely.

I also facing problem Here is the solution.

import React,{useState} from 'react';

import{Button,StyleSheet,View,TouchableOpacity,Modal} from 'react-native';

const App=()=>{
   const [show, setShow] = useState(false);
    return (
      <View>
        <TouchableOpacity style={{marginTop:200}}>
          <Button title='show' onPress={()=>setShow(!show)}/>
          </TouchableOpacity>   
            <Modal transparent={true} visible={show} animationType="slide">
            <TouchableOpacity activeOpacity={1} style={{backgroundColor:'#000000aa',flex:1}} onPress={()=>setShow(!show)}/>
            
            <View style={{backgroundColor:'#FFFFFF',flex: 1}}>
                <View >
                     <Button title='close' onPress={()=>setShow(!show)}/>
               </View>  
            </View>
        </Modal>
      </View>
    )};
export default App;

You Can Check Another Example Here Also:
https://snack.expo.dev/@saurabh_chavan/model-with-border-radius

Solution 12 - Javascript

If you are using store solution like mobx or redux, you can simply solve with the flag on store.

Below is the parent's code,

      <Modal
        animationType="fade"
        transparent={true}
        visible={uiControlStore.dialogVisible}
        onRequestClose={() => {
          uiControlStore.dialogVisible = false;
        }}
      >
        <View
          style={{ flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: 'rgba(0,0,0,0.6)' }}
          onTouchStart={() => {
            if (!uiControlStore.childClicked) uiControlStore.dialogVisible = false;
            uiControlStore.childClicked= false;
          }}
        >
          <YourCustomDialog />
        </View>
      </Modal>

and below is child's code. (using mobx)

const YourCustomDialog: React.FC = observer(() => {
  const { uiControlStore } = useStores();

  return (
    <View
      style={[styles.container, styles.border]}
      onTouchStart={() => {
        uiControlStore.childClicked= true;
      }}
    >
    ...
  )
}

Solution 13 - Javascript

I realize I'm very late to this party. But I just stumbled upon this thread and Gui Herzog's answer was exactly what I was looking for. If you don't want to add any outside dependencies that is the way to go. Thanks!

However, I wanted to include some optional negative/positive action buttons in my component and grabbed them from react-native-paper for the material style. That's when I realized react-native-paper probably have modals as well.

Here's their documentation: https://callstack.github.io/react-native-paper/modal.html<br /> They also have a component for Dialogs https://callstack.github.io/react-native-paper/dialog.html

So I ended up with using the paper Dialog and it's well worth if if you're going to use the library throughout you app. Both Dialog and Modal handles the dismiss on click outside by default.


Here's a Dialog component created before realizing the Dialog component already exists.

I'll leave what I implemented here anyways as I think its a good template.

The component is in typescript. Make sure to have @types/react-native updated otherwise you might see some "No overload matches this call" errors.

import React from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {Button, Modal, Portal} from 'react-native-paper';

interface Action {
  action: () => void;
  text: string;
}

interface Props {
  closePressed: () => void;
  negativeAction?: Action;
  positiveAction?: Action;
  title?: string;
  text: string;
  visible: boolean;
}

export const Dialog: React.FC<Props> = ({
  closePressed,
  negativeAction,
  positiveAction,
  title,
  text,
  visible,
}) => {
  return (
    <Portal>
      <Modal
        visible={visible}
        onDismiss={closePressed}
        contentContainerStyle={styles.modalContainer}>
        <View style={styles.header}>
          {title && (
            <Text style={{fontWeight: 'bold', fontSize: 18, marginBottom: 10}}>
              {title}
            </Text>
          )}
          <Text style={styles.contentText}>{text}</Text>
        </View>
        <View style={styles.buttonContainer}>
          {negativeAction && (
            <Button mode="text" onPress={negativeAction.action}>
              {negativeAction.text}
            </Button>
          )}
          {positiveAction && (
            <Button mode="text" onPress={positiveAction.action}>
              {positiveAction.text}
            </Button>
          )}
        </View>
      </Modal>
    </Portal>
  );
};

const styles = StyleSheet.create({
  modalContainer: {
    borderRadius: 5,
    backgroundColor: 'white',
    padding: 10,
    margin: 20,
  },
  header: {padding: 20},
  contentText: {color: 'grey'},
  buttonContainer: {
    flexDirection: 'row',
    justifyContent: 'flex-end',
    paddingTop: 20,
  },
});

Solution 14 - Javascript

Here is my perfectly working solution

MODAL CODE:

const ListInModal = ({ showListModal, onClose, data, onSelectItem }) => {
  return (
    <Modal animationType="none" transparent={true} visible={showListModal} onRequestClose={() => onClose(false)}>
      <TouchableOpacity onPress={() => onClose(false)} style={styles.centeredView}>
        <View style={styles.modalView}>
          <ScrollView showsVerticalScrollIndicator={false}>
            {data.map((item, index) => (
              <>
                <TouchableOpacity
                  onPress={() => {
                    onSelectItem(item);
                    onClose(false);
                  }}
                  style={{ height: 43, justifyContent: 'center' }}
                >
                  <Text style={styles.itemLabel}>{item.label}</Text>
                </TouchableOpacity>
                {index <= data.length - 2 && (
                  <View
                    style={{
                      borderBottomColor: colors.white,
                      opacity: 0.2,
                      borderWidth: 1,
                      marginHorizontal: (24 / 375) * screenWidth,
                    }}
                  />
                )}
              </>
            ))}
          </ScrollView>
        </View>
      </TouchableOpacity>
    </Modal>
  );
};

STYLING:

const styles = StyleSheet.create({
  centeredView: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: '#00000099',
  },
  modalView: {
    marginHorizontal: wp('5%'),
    marginVertical: hp('10%'),
    backgroundColor: colors.popupBlack,
    borderRadius: 20,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  itemLabel: {
    fontSize: wp('5%'),
    color: colors.white,
    paddingHorizontal: (24 / 375) * screenWidth,
  },
});

USAGE:

<ListInModal
  data={projectState?.lvApplicationTypeList}
  showListModal={showListModal}
  onClose={(bool) => setshowListModal(bool)}
  onSelectItem={(item) => onPressApplicationType(item.label)}
/>

Solution 15 - Javascript

I made it like this.

<Modal
      visible={isVisible}
      onRequestClose={() => setIsVisible(false)}
      transparent={true}
      
      >
          <Pressable style={{
              flex:1,
              backgroundColor:'transparent',
            
          }}
          onPress={()=>setIsVisible(false)}
          />
          {/* Here comes your component*/}
    </Modal>

make your component with position:absoute so Pressable can cover the whole background.

Solution 16 - Javascript

@idiosync/react-native-modal is a hook based modal system.

https://www.npmjs.com/package/@idiosync/react-native-modal

You can add a onBackgroundPress function to the config object to achieve what you want:

useModal(
    {
      // all config params are optional apart from renderModal
      renderModal: () => <MyModal onClose={() => setModalIsVisible(false)} someProp={someProp} />,
      onBackgroundPress: () => setModalIsVisible(false),
      animationTypeIn: AnimationType.SLIDE_TOP,
    },
    modalIsVisible,
    [someProp] // dependencies array to trigger rerenders. Empty array is passed by default
  )

Solution 17 - Javascript

I tried to implement some of the suggested answers, however none worked with buttons inside modal.

I was googling around and found out that instead of using TouchableWithoutFeedback, native component called Pressable allows you to check what you click on, therefore allows you to close only when clicked on it and not it's children.

return (
   <View>
      <Modal 
        animationType="slide"
        transparent={true}
        visible={visible}
        onRequestClose={() => {
           setVisible(false)}}
      >
        <Pressable
		  onPress={(event) => event.target == event.currentTarget && setVisible(false)}
		  style={styles.background}
		>
          <View style={styles.modal}>
             //Content of the modal
          </View>
        </Pressable>   
      </Modal> 
   </View>
)

Found the answer here.

Solution 18 - Javascript

2022 Answer - Using React Native Hooks

to Close a React Native Modal by clicking an Overlay is best done by using a Pressable button and TouchableOpacity

Example Below...

  • Import the Pressable and others from react-native
import React, { useState } from 'react';
import {
  Pressable,
  View,
  Text,
  TouchableOpacity,
  ScrollView,
  Modal,
  TextInput,
  StyleSheet,
} from 'react-native';
  • Then Set a State
const [modalVisible, setModalVisible] = useState(false);
  • Then Write and Design Your Modal
        <Modal
          animationType="slide"
          transparent={true}
          visible={modalVisible}
          onRequestClose={() => {
            setModalVisible(!modalVisible);
          }}
        >
          <TouchableOpacity
            onPress={() => setModalVisible(false)}
            style={styles.modalContainerStyle}
          >
            <Pressable
              onPress={() => setModalVisible(true)}
              style={styles.modalContent}
            >
              <ScrollView>
                <View>
                  <Text style={styles.modalTitle}>TITLE HERE</Text>

                  <Text style={styles.modalText}>
                    OTHER SMALLER TEXT HERE
                  </Text>

                  <Text style={styles.modalFormText}>AN INPUT</Text>
                  <TextInput
                    style={styles.modalInput}
                    value={inputValue}
                    onChangeText={(inputValue) => handleInputValue(inputValue)}
                    placeholder={'[email protected]'}
                  />

                  <View style={styles.btnView}>
                    <TouchableOpacity
                      onPress={() => setModalVisible(!modalVisible)}
                      underlayColor="white"
                    >
                      <View style={styles.buttonProceed}>
                        <Text style={styles.buttonText}>NEXT</Text>
                      </View>
                    </TouchableOpacity>
                  </View>
                </View>
              </ScrollView>
            </Pressable>
          </TouchableOpacity>
        </Modal>

  • Now To Open Your Modal (Using a Text Button)
<Pressable onPress={() => setModalVisible(!modalVisible)}>
  <Text style={styles.openModal}>Open Modal</Text>
</Pressable>
  • Finally Style Your Modal
modalContainerStyle: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'space-around',
    alignItems: 'flex-end',
    backgroundColor: 'rgba(0, 0, 0, 0.3)',
  },
  modalContent: {
    borderTopLeftRadius: 27,
    borderTopRightRadius: 27,
    width: '100%',
    height: '60%',
    backgroundColor: '#FFFFFF',
    paddingLeft: 33,
    paddingRight: 33,
    overflow: 'hidden',
  },
  modalTitle: {
    marginTop: 43,
    marginBottom: 14,
    fontSize: 19,
    lineHeight: 21,
    color: 'black',
    fontFamily: 'GraphikBold',
  },
  modalText: {
    fontSize: 15,
    lineHeight: 25,
    marginBottom: 38,
    color: 'black',
    fontFamily: 'GraphikRegular',
  },
  modalFormText: {
    marginBottom: 10,
    fontSize: 15,
    color: 'black',
    fontFamily: 'GraphikMedium',
  },
  modalInput: {
    marginBottom: 99,
    height: 44,
    paddingLeft: 17,
    paddingRight: 17,
    fontSize: 16,
    borderRadius: 10,
    marginBottom: 20,
    borderWidth: 1,
    fontFamily: 'GraphikRegular',
    borderColor: 'rgba(118, 118, 118, 0.17)',
  },
  openModal: {
    marginRight: 5,
    textAlign: 'right',
    color: '#ED2125',
    fontFamily: 'GraphikMedium',
  },

Solution 19 - Javascript

I have solved this in react native 0.64 by given below code

 <Modal  
     isVisible={ModalVisible}  

      onBackdropPress={()=>{setModalVisible(false)}}
       >
      .
      .
      . 
  </Modal>

Solution 20 - Javascript

Hi I am using a lightweight popup react-native-easypopup it also closing itself when you tap out of popup.

npm i react-native-easypopup

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionaciderntView Question on Stackoverflow
Solution 1 - JavascriptGui HerzogView Answer on Stackoverflow
Solution 2 - JavascriptsorratView Answer on Stackoverflow
Solution 3 - JavascriptAleksa RadovanovicView Answer on Stackoverflow
Solution 4 - JavascriptSimple AbstractionView Answer on Stackoverflow
Solution 5 - JavascriptMichael WallaceView Answer on Stackoverflow
Solution 6 - JavascriptlostomatoView Answer on Stackoverflow
Solution 7 - JavascriptKim HanSolView Answer on Stackoverflow
Solution 8 - JavascriptskmakView Answer on Stackoverflow
Solution 9 - JavascriptdecoderView Answer on Stackoverflow
Solution 10 - JavascriptMarvinView Answer on Stackoverflow
Solution 11 - JavascriptSaurabh ChavanView Answer on Stackoverflow
Solution 12 - JavascriptbychungView Answer on Stackoverflow
Solution 13 - JavascriptTejpbitView Answer on Stackoverflow
Solution 14 - JavascriptAjmal HasanView Answer on Stackoverflow
Solution 15 - Javascript박제영View Answer on Stackoverflow
Solution 16 - JavascriptJames TrickeyView Answer on Stackoverflow
Solution 17 - JavascriptTomasBView Answer on Stackoverflow
Solution 18 - JavascriptFrenzyNwobaView Answer on Stackoverflow
Solution 19 - JavascriptAwais QadirView Answer on Stackoverflow
Solution 20 - JavascriptyowovoguView Answer on Stackoverflow