How to close a React Native modal with a button | Java code geeks


I recently worked with React Native and encountered a few unimplemented features that I needed to add to the core components. One of these components is the React Native Modal component used to display one view on top of another. It doesn’t promise more features than that, but it can be very useful to implement your own popup.

On the flip side, since it’s just a simple modal, it doesn’t include everything you’d expect from a context component like an X to close it or close the modal when you tap next to it. of it. I will walk you through how to add these two features when you start from a Modal making up.

If you are in a hurry, you can also find the final result on my GitHub: https://github.com/CindyPotvin/react-native-popup

How to close a modal

Let’s start with a base App.js which displays a pop-up window. I also added a few styles to have a shadow so that the pop-up is visible, and a generous margin so that we can test closing the pop-up by clicking next to it later.

import React, { useState } from 'react';
import { StyleSheet, Modal, Text, View } from 'react-native';

export default function App() {
  const [modalVisible, setModalVisible] = useState(true);

  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</text>
      <Modal visible={modalVisible}>
        <View style={styles.modal}>
          <Text>
            Popup content.
          </Text>
        </View>
      </Modal>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
  },
  modal: {
    flex: 1,
    margin: 50,
    padding: 5,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    elevation: 5,
  },
});

The Modal is displayed according to its visible to support. Currently, the state value is always true, so the pop-up will still be visible.

Even without a button or other way to change the state, the Modal can be closed on Android with the back button (the menu button on Apple TV should have the same effect according to the documentation, but I don’t have one to test).

Pressing the button calls up the function specified in the onRequestClose prop of the modal, so let’s change the state to hide the popup when pressed, which will display the main app screen:

<Modal
  visible={modalVisible}
  onRequestClose={() => setModalVisible(false)}>

How to add a close button to a React Native modal

First of all, we need to add the button itself so that we can then use it to close the pop-up window. In this case, I wanted to add a little X in the top right corner of the pop-up, but it could be anywhere else.

Given how positioning works, there are two options for this:

  • Absolutely positioning the button in the upper right corner. You can then do whatever you want for the rest of the content, but then you run the risk of the content overlapping the X because it comes out of the normal layout flow. If you want to use the space next to and under the button at the same time, that’s pretty much impossible, which brings us to the second option.
  • Positioning of the button with flexbox, leaving space to the left of the button for a header. You can then fill in the header and content at the bottom separately. If you’re doing something that’s supposed to be a pop-up, having a title is also a pretty standard feature.

This is what the component with an added X now looks like:

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

export default function App() {
  const [modalVisible, setModalVisible] = useState(true);

  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
      <Modal visible={modalVisible} onRequestClose={() => setModalVisible(false)}>
        <View style={styles.modal}>
          <View style={styles.modalHeader}>
            <View style={styles.modalHeaderContent}><Text>Other header content</Text></View>
            <TouchableOpacity><Text style={styles.modalHeaderCloseText}>X</Text></TouchableOpacity>
          </View>
          <View style={styles.modalContent}>
            <Text>
              Popup content.
            </Text>
          </View>
        </View>
      </Modal>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
  },
  modal: {
    flex: 1,
    margin: 50,
    padding: 5,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    elevation: 5,
  },
  /* The content of the modal takes all the vertical space not used by the header. */
  modalContent: {
    flex: 1
  },
  modalHeader: {
    flexDirection: "row",
  },
  /* The header takes up all the vertical space not used by the close button. */
  modalHeaderContent: {
    flexGrow: 1,
  },
  modalHeaderCloseText: {
    textAlign: "center",
    paddingLeft: 5,
    paddingRight: 5
  }
});

The header is a flexible container that appears as a row, with flexGrow:1 à indicates that it should occupy all the remaining space.

The rest of the pop-up content is a flex:1 element so that it occupies all the remaining height.

The only thing left at this point is to wire the button to close the pop-up, the same way we put it on the onRequestClose earlier event:

<TouchableOpacity onPress={() => setModalVisible(false)}>
  <Text style={styles.modalHeaderCloseText}>X</Text>
</TouchableOpacity>

How to close the modal by clicking outside

To also close the modal by tapping on the outside, we need an extra component to catch those tapping.

On the other hand, we don’t want this component to catch the taps intended for the child component: clicking on the popup itself shouldn’t close it. While verifying event.target == event.currentTarget, we validate that the selected element is the component itself and not one of its children.

i used a Squeezable component because I didn’t want the effect of “dimming” on the tap by touching outside of the pop-up that came with the TouchableOpacity. This new Squeezable component wraps all the components we previously defined in the modal.

Here’s the completed pop-up, with a few more borders to show where the header and content of the pop-up is:

import React, { useState } from 'react';
import { StyleSheet, Modal, Text, View, Pressable, TouchableOpacity } from 'react-native';

export default function App() {
  const [modalVisible, setModalVisible] = useState(true);
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
      <Modal
        visible={modalVisible}
        onRequestClose={() => setModalVisible(false)}>
        <Pressable style={styles.outsideModal}
          onPress={(event) => { if (event.target == event.currentTarget) { 
            setModalVisible(false); } }} >
          <View style={styles.modal}>
            <View style={styles.modalHeader}>
              <View style={styles.modalHeaderContent}><Text>Other header content</Text></View>
              <TouchableOpacity onPress={() => setModalVisible(false)}>
                <Text style={styles.modalHeaderCloseText}>X</Text>
              </TouchableOpacity>
            </View>
            <View style={styles.modalContent}>
              <Text>
                Popup content.
              </Text>
            </View>
          </View>
        </Pressable>
      </Modal>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
  },
  modal: {
    flex: 1,
    margin: 50,
    padding: 5,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    elevation: 5,
  },
  /* The content of the modal takes all the vertical space not used by the header. */
  modalContent: {
    flex: 1,
    borderWidth: 1,
    borderColor: "black"
  },
  modalHeader: {
    flexDirection: "row",
    borderWidth: 1,
    borderColor: "black"
  },
  /* The header takes up all the vertical space not used by the close button. */
  modalHeaderContent: {
    flexGrow: 1,
  },
  modalHeaderCloseText: {
    textAlign: "center",
    paddingLeft: 5,
    paddingRight: 5
  },
  outsideModal: {
    backgroundColor: "rgba(1, 1, 1, 0.2)",
    flex: 1,
  }
});

Please note that there is a small limitation to this: the background color for a Squeezable or one TouchableOpacity cannot be set to a transparent or semi-transparent value, so the content below the pop-up will no longer be visible.

A next step to improve this would be to pack the Modal components and all of its content into a new component that can be reused in your app so that you can insert any header or content into the pop-up, but that’s outside the scope of the current article.

If you want to run the final version yourself and try it out, you can see it on GitHub here: https://github.com/CindyPotvin/react-native-popup and as Expo Snack here: https://snack.expo.dev/@cindyptn/react-native-popup-with-x-button



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *