DEV Community

Ezea Victor
Ezea Victor

Posted on • Originally published at ezeavictor.hashnode.dev on

How to Build Custom Modal in React Native

Animation has become an integral part of mobile development, thanks to React native for its Animated API. In this, we will be building a custom modal with the help of react native animated API using just a button click.

A Modal component is a basic way to present content above an enclosing view.

Prerequisite

  1. Node >= 16.0.0

  2. Expo CLI

To get started with the custom modal we need to generate a new expo project with the following command line.

npx create-expo-app -t expo-template-blank-typescript

cd into the project and run:

yarn install @expo/vector-icons

yarn start or npm start

Screen Layout

In the project, directory create a screen folder and a tsx file with the name src/Screens/ModalScreen.tsx

import React, { useState } from "react";
import { View, StyleSheet, Image, Text, TouchableOpacity } from "react-native";

const ModalScreen = () => {
  return (
    <View>
      <Text> Modal Screen</Text>
    </View>
  );
};

export default ModalScreen;

Enter fullscreen mode Exit fullscreen mode

We also need to create the modal component, inside the project directory create the component folder Components/Modals/ModalLayout.tsx

import { View, Text, StyleSheet, Modal, Animated } from "react-native";
import React from "react";

const ModalLayout = () => {
  return (
    <View>
      <Text>Modal Layout</Text>
    </View>
  );
};

export default ModalLayout;

Enter fullscreen mode Exit fullscreen mode

At the ModalLayout.tsx we need to import react hooks useEffect, useState, useRef to manage our states and also the Animated API together with Modal component from react-native.

The Animated library is designed to make animations fluid, powerful, and painless to build and maintain. Animated focuses on declarative relationships between inputs and outputs, configurable transforms in between, and start/stop methods to control time-based animation execution.

To calculate the scale value of our animation, we will use Animated value type Animated.Value() for single values.

  const scaleValue = useRef(new Animated.Value(0)).current;

Enter fullscreen mode Exit fullscreen mode

A function toggleModal is created to help with the animation mechanism.

 const scaleValue = useRef(new Animated.Value(0)).current;
  const [showModal, setShowModal] = useState(visible);

  useEffect(() => {
    toggleModal();
  }, [visible]);

  const toggleModal = () => {
    if (visible) {
      setShowModal(true);
      Animated.spring(scaleValue, {
        toValue: 1,
        useNativeDriver: true,
      }).start();
    } else {
      setTimeout(() => setShowModal(false), 200);
      Animated.timing(scaleValue, {
        toValue: 0,
        duration: 300,
        useNativeDriver: true,
      }).start();
    }
  };

Enter fullscreen mode Exit fullscreen mode

The full ModalLayout.tsx will look like this.

import { View, StyleSheet, Modal, Animated } from "react-native";
import React, { useState, useRef, useEffect } from "react";
import { COLORS } from "../../utils";

type Props = {
  visible: boolean;
  children: ReactNode;
};

const ModalLayout = ({ children, visible }: Props) => {
  const scaleValue = useRef(new Animated.Value(0)).current;
  const [showModal, setShowModal] = useState(visible);

  useEffect(() => {
    toggleModal();
  }, [visible]);

  const toggleModal = () => {
    if (visible) {
      setShowModal(true);
      Animated.spring(scaleValue, {
        toValue: 1,
        useNativeDriver: true,
      }).start();
    } else {
      setTimeout(() => setShowModal(false), 200);
      Animated.timing(scaleValue, {
        toValue: 0,
        duration: 300,
        useNativeDriver: true,
      }).start();
    }
  };

  return (
    <Modal transparent visible={showModal}>
      <View style={styles.container}>
        <Animated.View
          style={[styles.modalCon, { transform: [{ scale: scaleValue }] }]}
        >
          {children}
        </Animated.View>
      </View>
    </Modal>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: COLORS.modalbackgroundcolor,
  },

  modalCon: {
    backgroundColor: "white",
    paddingHorizontal: 20,
    paddingVertical: 30,
    borderRadius: 20,
    elevation: 20,
    width: "80%",
  },
});

export default ModalLayout;

Enter fullscreen mode Exit fullscreen mode

Button Component

create Button.tsx inside the project directory src/Components/Buttons/Button.tsx

import { Text, TouchableOpacity, StyleSheet } from "react-native";
import React from "react";
import { COLORS } from "../../utils";

type ButtonProps = {
  onPress: () => void;
  title: string;
};

const Button = ({ onPress, title }: ButtonProps) => {
  return (
    <TouchableOpacity
      onPress={onPress}
      activeOpacity={0.9}
      style={styles.button}
    >
      <Text style={styles.title}>{title}</Text>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  button: {
    height: 55,
    width: "65%",
    backgroundColor: COLORS.red,
    marginVertical: 10,
    justifyContent: "center",
    alignItems: "center",
    borderRadius: 5,
  },
  title: {
    color: COLORS.white,
    fontWeight: "bold",
    fontSize: 18,
  },
});

export default Button;

Enter fullscreen mode Exit fullscreen mode

Modal Screen

In the modal screen import ModalLayout.tsx and Button.tsx components inside ModalScreen.tsx go ahead and import Entypo from @expo/vector-icons

import React, { useState } from "react";
import { View, StyleSheet, Image, Text, TouchableOpacity } from "react-native";

import { Button } from "../Components/Buttons";
import { Entypo } from "@expo/vector-icons";

import ModalLayout from "../Components/Modals/ModalLayout";

const ModalScreen = () => {
  const [visible, setVisible] = useState(false);

  return (
    <View style={styles.container}>
      <ModalLayout visible={visible}>
        <View style={{ alignItems: "center" }}>
          <View style={styles.header}>
            <TouchableOpacity onPress={() => setVisible(false)}>
              <Entypo
                name="cross"
                style={{ height: 30, width: 30 }}
                size={24}
                color="black"
              />
            </TouchableOpacity>
          </View>
        </View>
        <View style={{ alignItems: "center" }}>
          <Image
            source={require(".././assets/imageIcons/success.png")}
            style={{ height: 150, width: 150, marginVertical: 10 }}
          />
        </View>

        <Text style={{ marginVertical: 30, fontSize: 20, textAlign: "center" }}>
          Transaction Successful
        </Text>
      </ModalLayout>
      <Button title="Show Modal" onPress={() => setVisible(true)} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  header: {
    width: "100%",
    height: 40,
    alignItems: "flex-end",
    justifyContent: "center",
  },
});

export default ModalScreen;

Enter fullscreen mode Exit fullscreen mode

create src/utils/Colors.tsx in the project directory

const COLORS = {
  modalbackgroundcolor: "rgba(0,0,0,0.5)",
  modalbackgroundContainerColor: "white",
  white: "#FFFFFF",
  red: "#FF0000",
};

export default COLORS;

Enter fullscreen mode Exit fullscreen mode

The custom modal should look like this

GitHub link to the tutorial: Github Link

Conclusion

The tutorial completes how we can use Animated API and Modal component from react-native to build our custom modal in our mobile projects. kindly share this article and follow on Twitter

Top comments (0)