The Widlarz Group Blog

React Native Modals

December 10, 2021

modal

react native

mobile

react native modal

Introduction

The quality of an application or a website depends on the time that users spend in it. Creators do their best to make the best products that, will engage users and not bore them to death. But nowadays simple navigating between pages/screens or scrolling is not enough. The users need to feel alive every single second. What is one of the best tools to engage them more? The answer is modals - popup dialog windows for informing, warning, or stimulating user action. The majority of the best products use them, and the rest should start doing so today.

In this article, I will present examples of modals usage in the world of React-Native. RN has a built-in modal component, but there are many noteworthy libraries, which enhance their capabilities. One of the most popular is the react-native-modal, and that’s not without a reason! I will show you how to use this library to create interesting and quite simple solutions, which will definitely find a place in your own project.

Below is a little sneak peek of the modals we will create in this project.

Sneak peek

Content

Project Setup

First of all, we need to clone the project repository. The finished project is on the master branch, but I encourage you to use the starter branch, which I’ve created for you to code by yourself.

// starter
git clone -b starter https://github.com/TheWidlarzGroup/react-native-modals
// or master
git clone https://github.com/TheWidlarzGroup/react-native-modals

Install packages:

cd react-native-modals && yarn install

For iOS:

cd ios && pod install

Run the project on a simulator or a physical device:

yarn ios
// or
yarn android

Playground Intro

As you probably saw in the sneak peek, I created a transparent playground for this project. It contains a bottom bar navigation, which directs you to four different screens. Every single screen has custom buttons, which will be used for firing up the modals. Three screens - Slide, Fade, Rotate will give you possibilities of making cool animated modals which you will find in the most popular products. The last screen - Utils is for modals with extra functionalities, which you can implement wherever you need, and will definitely take your project to the next level! That’s all waiting for you in the starter branch. No more time to waste, let’s jump into the world of modals!

Library Intro

In this chapter, I will present tools from the react-native-modal library. It’s fully functional and basically all you need to create great modals.

Props

To get full control of the created modal, you need to pass certain props. Below is a list of props used in this project. Please check it out to feel more comfortable in the next chapters:

  • animationIn - show animation type,
  • animationInTiming - show animation duration,
  • animationOut - hide animation type,
  • animationOutTiming - hide animation duration,
  • backdropOpacity - backdrop opacity for visible modal,
  • backdropTransitionInTiming - show backdrop transition duration,
  • backdropTransitionOutTiming - hide backdrop transition duration,
  • isVisible - show/hide modal,
  • onBackButtonPress - function called on Android back button press,
  • onBackdropPress - function called on backdrop press,
  • onSwipeComplete - function called when swipe is complete,
  • swipeDirection - direction where modal can be swiped.

Animations

Apart from styling, you can get most of the user impressions with implementing cool animations. As you probably saw, we can define certain animation types for showing and hiding the modal. Below is a short list of some animations which you can implement directly from this library:

  • slideInUp - slide in from bottom to top,
  • slideOutDown- slide out from top to bottom,
  • fadeInUp- fade in with slide from bottom to top,
  • fadeOut - fade out,
  • shake - show in with shake vibrations,
  • rotate - rotation before show or hide.

These are only a few from a huge list of built-in library animations. Feel free to check them out in the library documentation.

The most important component we need to create is a modal. It must be reusable because we want to use this component repeatedly in different forms.

Customizable component

Let’s build the modal structure. We can import its container from the react-native-modal library, as well as its Props, which will help us create a generic and fully customizable component.

As for the props, we pass:

  • hideModal - function closing the modal,
  • hideCloseButton - boolean to hide close modal button,
  • style - styles of the modal.
import React from 'react';
import {View, StyleSheet, StyleProp, ViewStyle, Text} from 'react-native';
import {theme} from '../../theme/theme';

import Modal, {ModalProps} from 'react-native-modal';
import ButtonComponent from '../Button/ButtonComponent';

interface Props {
  hideModal: () => void;
  hideCloseButton?: boolean;
  style?: StyleProp<ViewStyle>;
}

// ModalComponent.tsx

The component body needs to be wrapped with the Modal, which takes the rest of ...props - this is where we pass library-specific props. Inside we nest a View container, which takes style props from a parent. With the hideCloseButton prop you can define if the close button is needed or not. It’s not necessary, because Modal with proper props will hide after a click on its backdrop. If you don’t find any method satisfactory, the modal can hide even on swipe, which we will implement later. Inside you can put whatever you want to, and if you want to pass dynamic content, just use children. We also add some specified text, to show it in every modal component.

const ModalComponent = ({
  hideModal,
  hideCloseButton,
  style,
  children,
  ...props
}: Props & Partial<ModalProps>) => {
  return (
    <Modal {...props}>
      <View style={[styles.modal, style]}>
        <Text style={styles.text}>Place for your content</Text>
        {!hideCloseButton && (
          <ButtonComponent
            title="Close"
            style={styles.closeButton}
            onClick={hideModal}
          />
        )}
        {children}
      </View>
    </Modal>
  );
};

// ModalComponent.tsx

Modal implementation into the screen is a piece of cake. We need to import the recently created modal component and add it with proper props. The most significant prop is the isVisible, it’s a boolean which tells the modal to show or hide. The best way to control it is by using a boolean variable from the useState hook - in the below code, it’s a showFirstModal. If it’s true, the modal will show, otherwise it will hide. We have a button, which will set it to true on click. Whenever we want to hide the modal, we just need to change showFirstModal to false. That’s the reason why we pass in props onBackdropPress and onBackButtonPress functions which sets it to false. As their names suggest, these props will do some action when clicking outside of the modal or on the back button, which is closing the modal in our case. This is a regular approach to close any modals.

import ModalComponent from '../components/Modal/ModalComponent';

const SlideScreen = () => {
  const [showFirstModal, setShowFirstModal] = useState(false);

  return (
    <View>
      <ButtonComponent
        onClick={() => setShowFirstModal(true)}
      />
      <ModalComponent
        isVisible={showFirstModal}
        onBackdropPress={() => setShowFirstModal(false)}
        onBackButtonPress={() => setShowFirstModal(false)}
        style={styles.topModal}
      />
    </View>
  );
};

// SlideScreen.tsx

Modals examples

To create awesome modals we need to combine props and animations mentioned in the previous chapter. I will show you some examples of my implementations. They can be easily modified, so feel free to experiment with your own ideas!

Slide animations modals

First modal - specification:

  • small size,
  • slides in from the top,
  • slides out to the top,
  • hides after swipe to the top.
  <ModalComponent
    isVisible={showFirstModal}
    onBackdropPress={() => setShowFirstModal(false)}
    onBackButtonPress={() => setShowFirstModal(false)}
    swipeDirection="up"
    onSwipeComplete={() => setShowFirstModal(false)}
    hideModal={() => setShowFirstModal(false)}
    animationIn="slideInDown"
    animationOut="slideOutUp"
    style={styles.topModal}
  />

  const styles = StyleSheet.create({
    topModal: {
      flex: 1 / 3,
    },
  });

// SlideScreen.tsx

Slide screen first modal

Second modal - specification:

  • big size,
  • slides in from the left,
  • slides out to the left,
  • hides after swipe to the left,
  • longer animations duration and backdrop transition.
  <ModalComponent
    isVisible={showSecondModal}
    onBackdropPress={() => setShowSecondModal(false)}
    onBackButtonPress={() => setShowSecondModal(false)}
    swipeDirection="left"
    onSwipeComplete={() => setShowSecondModal(false)}
    hideModal={() => setShowSecondModal(false)}
    animationIn="slideInLeft"
    animationInTiming={800}
    animationOut="slideOutLeft"
    animationOutTiming={800}
    backdropTransitionInTiming={800}
    backdropTransitionOutTiming={800}
  />

// No styles passed

// SlideScreen.tsx

Slide screen second modal

Third modal - specification:

  • almost fullscreen size,
  • slides in from the right with small bounce,
  • slides out to the right with small bounce,
  • hides after swipe to the right,
  • shorter animations duration and backdrop transition,
  • backdrop without opacity.
  <ModalComponent
    isVisible={showThirdModal}
    onBackdropPress={() => setShowThirdModal(false)}
    onBackButtonPress={() => setShowThirdModal(false)}
    swipeDirection="right"
    onSwipeComplete={() => setShowThirdModal(false)}
    hideModal={() => setShowThirdModal(false)}
    animationIn="bounceInRight"
    animationInTiming={500}
    animationOut="bounceOutRight"
    animationOutTiming={500}
    backdropTransitionOutTiming={500}
    backdropOpacity={1}
    style={styles.rightModal}
  />

  const styles = StyleSheet.create({
    rightModal: {
      flex: 1,
    },
  });

// SlideScreen.tsx

Slide screen third modal

Fourth modal - specification:

  • small size,
  • slides in from the bottom with small bounce,
  • slides out to the bottom with small bounce,
  • hides after swipe to the bottom,
  • shorter animations duration and backdrop transition,
  • positioned at the bottom.
  <ModalComponent
    isVisible={showFourthModal}
    onBackdropPress={() => setShowFourthModal(false)}
    onBackButtonPress={() => setShowFourthModal(false)}
    swipeDirection="down"
    onSwipeComplete={() => setShowFourthModal(false)}
    hideModal={() => setShowFourthModal(false)}
    animationIn="bounceInUp"
    animationInTiming={500}
    animationOut="bounceOutDown"
    animationOutTiming={500}
    style={styles.bottomModal}
  />

  const styles = StyleSheet.create({
    bottomModal: {
      height: 150,
      width: width - 20,
      position: 'absolute',
      bottom: -10,
      left: -10,
      borderRadius: 10,
    },
  });

// SlideScreen.tsx

Slide screen fourth modal

Fade animations modals

First modal - specification:

  • big size,
  • shows with fade in and slide to the bottom,
  • hides with fade out and slide to the top.
  <ModalComponent
    isVisible={showFirstModal}
    onBackdropPress={() => setShowFirstModal(false)}
    onBackButtonPress={() => setShowFirstModal(false)}
    hideModal={() => setShowFirstModal(false)}
    animationIn="fadeInDown"
    animationOut="fadeOutUp"
  />

// No styles

// FadeScreen.tsx

Fade screen first modal

Second modal - specification:

  • small size,
  • shows with fade in and small pulse,
  • hides with fade out.
  <ModalComponent
    isVisible={showSecondModal}
    onBackdropPress={() => setShowSecondModal(false)}
    onBackButtonPress={() => setShowSecondModal(false)}
    hideModal={() => setShowSecondModal(false)}
    animationIn="pulse"
    animationOut="fadeOut"
    style={styles.smallModal}
  />

  const styles = StyleSheet.create({
    smallModal: {
      flex: 1 / 3,
    },
  });

// FadeScreen.tsx

Fade screen second modal

Third modal - specification:

  • big size,
  • shows with big bang entrance,
  • hides with fade out,
  • backdrop without opacity.
  <ModalComponent
    isVisible={showThirdModal}
    onBackdropPress={() => setShowThirdModal(false)}
    onBackButtonPress={() => setShowThirdModal(false)}
    hideModal={() => setShowThirdModal(false)}
    animationIn="tada"
    animationOut="fadeOut"
    backdropOpacity={1}
  />

// No styles

// FadeScreen.tsx

Fade screen third modal

Fourth modal - specification:

  • small size,
  • shows with fade in and small shake,
  • shorter show in animation duration,
  • hides with fade out,
  • positioned at the bottom.
  <ModalComponent
    isVisible={showFourthModal}
    onBackdropPress={() => setShowFourthModal(false)}
    onBackButtonPress={() => setShowFourthModal(false)}
    hideModal={() => setShowFourthModal(false)}
    animationIn="shake"
    animationInTiming={500}
    animationOut="fadeOut"
    style={styles.bottomModal}
  />

 const styles = StyleSheet.create({
    bottomModal: {
    height: 150,
    width: width - 20,
    position: 'absolute',
    bottom: -10,
    left: -10,
    borderRadius: 10,
    },
  });

// FadeScreen.tsx

Fade screen fourth modal

Rotate animations modals

First modal - specification:

  • big size,
  • shows with rotation,
  • shorter show in animation duration and backdrop transition,
  • hides with slide out to the right,
  • backdrop with less opacity.
  <ModalComponent
    isVisible={showFirstModal}
    onBackdropPress={() => setShowFirstModal(false)}
    onBackButtonPress={() => setShowFirstModal(false)}
    hideModal={() => setShowFirstModal(false)}
    animationIn="rotate"
    animationInTiming={500}
    animationOut="slideOutRight"
    backdropOpacity={0.8}
    backdropTransitionOutTiming={500}
  />

// No styles

// RotateScreen.tsx

Rotate screen first modal

Second modal - specification:

  • small size,
  • rounded shape,
  • shows with rotation,
  • hides with slide out to the bottom,
  • shorter show in animation duration and backdrop transition,
  • backdrop with less opacity,
  • positioned to the right bottom corner.
  <ModalComponent
    isVisible={showSecondModal}
    hideCloseButton
    onBackdropPress={() => setShowSecondModal(false)}
    onBackButtonPress={() => setShowSecondModal(false)}
    hideModal={() => setShowSecondModal(false)}
    animationIn="rotate"
    animationInTiming={600}
    animationOut="slideOutDown"
    backdropTransitionInTiming={600}
    backdropOpacity={0.7}
    style={styles.circleBottomRightModal}
  />

const styles = StyleSheet.create({
  circleBottomRightModal: {
    position: 'absolute',
    bottom: 0,
    right: 0,
    width: 200,
    height: 200,
    borderRadius: 200,
  },
});

// RotateScreen.tsx

Rotate screen second modal

Third modal - specification:

  • medium size,
  • rounded shape,
  • shows with rotation,
  • hides with rotation,
  • shorter show in animation duration and backdrop transition,
  • backdrop with no opacity,
  • positioned centered.
<ModalComponent
    isVisible={showThirdModal}
    hideCloseButton
    onBackdropPress={() => setShowThirdModal(false)}
    onBackButtonPress={() => setShowThirdModal(false)}
    hideModal={() => setShowThirdModal(false)}
    animationIn="rotate"
    animationInTiming={500}
    animationOut="rotate"
    animationOutTiming={500}
    backdropOpacity={1}
    backdropTransitionInTiming={500}
    backdropTransitionOutTiming={500}
    style={styles.circleCenteredModal}
  />

const styles = StyleSheet.create({
  circleCenteredModal: {
    position: 'absolute',
    top: heigth / 2 - 169,
    left: width / 2 - 169,
    width: 300,
    height: 300,
    borderRadius: 200,
  },
});

// RotateScreen.tsx

Rotate screen third modal

Extra functional modals

Modals are not only used to show some information to users, they can be more functional. In this chapter, I will present two practical uses.

Confirmation wrapper

First comes the confirmation wrapper modal. It pops up to ask the user for confirmation. You can wrap it on any button and pass in any action which will be triggered only after confirmation. In fact, you can use it anywhere.

Component

The component itself is mainly a View container with its own modal nested inside, which is controlled by a boolean state. The confirmation button in this modal should trigger a function passed to this wrapper. Cancel just hides the modal. The most important thing is to use React.cloneElement on children (button passed in), to add the onClick action, which will show the modal on button press.

interface Props {
  onConfirm: () => void;
  children: ReactElement<any, string | JSXElementConstructor<any>>;
  style?: StyleProp<ViewStyle>;
}

const ConfirmationWrapper = ({
  onConfirm,
  style,
  children,
}: Props & Partial<ModalProps>) => {
  const [isModalOpen, setIsModalOpen] = useState(false);

  const handleConfirm = () => {
    onConfirm();
    setIsModalOpen(false);
  };

  return (
    <View>
      <Modal
        isVisible={isModalOpen}
        onBackdropPress={() => setIsModalOpen(false)}
        onBackButtonPress={() => setIsModalOpen(false)}
        swipeDirection="up"
        onSwipeComplete={() => setIsModalOpen(false)}
        animationIn="slideInDown"
        animationOut="slideOutUp">
        <View style={[styles.modal, style]}>
          <Text style={styles.text}>Are you sure?</Text>
          <View style={styles.buttonsContainer}>
            <ButtonComponent title="Confirm" onClick={handleConfirm} />
            <ButtonComponent
              title="Cancel"
              onClick={() => setIsModalOpen(false)}
            />
          </View>
        </View>
      </Modal>

      {React.cloneElement(children, {
        onClick: () => setIsModalOpen(true),
      })}
    </View>
  );
};

// Styles

// ConfirmationWrapper.tsx

Implementation

To implement a confirmation wrapper you just need to wrap it on any button and pass onConfirm with the action it should trigger. As simple as that. There is no need to add a new state to control the modal, everything is hidden inside.

const UtilsScreen = () => {
  return (
    <View>
      <ConfirmationWrapper
        onConfirm={() => Alert.alert('Your action was confirmed!')}>
        <ButtonComponent />
      </ConfirmationWrapper>
    </View>
  );
};

// UtilsScreen.tsx

Utils screen first modal

Swipe picker

The second example is a swipe picker modal. It is made to let the user swipe it to left or right. Different swipe directions will trigger different actions, which you can define as you want.

Component

In this component, we need to know which side the user picks. We save it in the state as 'left' or 'right'. To check swipe directions we can use onLayout handler, which is a standard react-native View component prop. It gives us an event which is triggered when a component layout has been calculated. With it, we can define horizontal position x, and set a proper direction in the state. The last thing is to trigger a proper function with onSwipeComplete props.

interface Props {
  isVisible: boolean;
  hideModal: () => void;
  swipeLeftAction: () => void;
  swipeRightAction: () => void;
  style?: StyleProp<ViewStyle>;
}

type SwipeDirection = 'left' | 'right';

const SwipePicker = ({
  isVisible,
  hideModal,
  swipeLeftAction,
  swipeRightAction,
  style,
}: Props & Partial<ModalProps>) => {
  const [swipeDirection, setSwipeDirection] = useState<SwipeDirection>('left');

  const handleSwipe = () => {
    hideModal();
    if (swipeDirection === 'left') {
      swipeLeftAction();
    } else {
      swipeRightAction();
    }
  };

  const handleSwipeDirectionSet = (positionX: number) => {
    if (positionX > 20 && swipeDirection === 'left') {
      setSwipeDirection('right');
    }
    if (positionX < 20 && swipeDirection === 'right') {
      setSwipeDirection('left');
    }
  };

  return (
    <Modal
      isVisible={isVisible}
      onBackdropPress={hideModal}
      onBackButtonPress={hideModal}
      swipeDirection={['left', 'right']}
      onSwipeComplete={handleSwipe}
      animationIn="slideInDown"
      animationOut="slideOutUp"
      onLayout={event => {
        const positionX = event.nativeEvent.layout.x;
        handleSwipeDirectionSet(positionX);
      }}>
      <View style={[styles.modal, style]}>
        <Text style={styles.text}>Swipe left or right</Text>
      </View>
    </Modal>
  );
};

// Styles

// SwipePicker.tsx

Implementation

As for the implementation, we use it as a normal modal controlled with the boolean state. One thing we need to do is pass our functions as swipeLeftAction and swipeRightAction and start swiping!

const UtilsScreen = () => {
  const [showSwipePicker, setShowSwipePicker] = useState(false);

  return (
    <View>
      <ButtonComponent onClick={() => setShowSwipePicker(true)} />
      <SwipePicker
        isVisible={showSwipePicker}
        hideModal={() => setShowSwipePicker(false)}
        swipeLeftAction={() => Alert.alert('You swiped left!')}
        swipeRightAction={() => Alert.alert('You swiped right!')}
      />
    </View>
  );
};

// UtilsScreen.tsx

Utils screen second modal

Conclusion

As you can see there are plenty of possibilities to create interesting modals. Not only cool looking and animated, but also fully functional ones. This project is a playground, and you play with however you want! I encourage you to change the styling, modify the animations or add your own functionalities. The created components are ready to put into your own projects. Just fill them with your own ideas.

Thank you for reading. I hope that you enjoyed it and found something interesting to use in your own projects.


Written by Bartek Mogielnicki.