Skip to content

GSTJ/react-native-magic-modal

Repository files navigation

React Native Magic Modal πŸ¦„

React Native Magic Modal Banner

Docs | GitHub | FAQ | Article

Note

Simplify your modal management in React Native with the React Native Magic Modal library. Effortlessly control modals, streamline complex flows, and create a seamless user experience.

Tip

Our new version just got released with full support for multiple modals! See the breaking changes.

Features

Highlights

React Native Magic Modal offers a superior experience compared to traditional modal implementations:

Table of Contents

Installation

Add peer dependencies to your project, if you haven't already:

yarn add react-native-reanimated
yarn add react-native-gesture-handler

Install the package:

yarn add react-native-magic-modal

Quickstart

Insert a MagicModalPortal at the top of your application structure, and a GestureHandlerRootView if you haven't already:

import { MagicModalPortal } from "react-native-magic-modal";
import { GestureHandlerRootView } from "react-native-gesture-handler";

export default function App() {
  return (
    <GestureHandlerRootView>
      <YourAppContent />
      <MagicModalPortal /> {/** After your app component hierarchy */}
    </GestureHandlerRootView>
  );
}

Tip: the root _layout.tsx is usually the best place to put it in a project using expo-router.

Examples

Showcasing modal management on iOS and Android platforms:

iOS Android

Usage

Here's the preferred usage pattern for the library:

import React from "react";
import { View, Text, TouchableOpacity } from "react-native";
import {
  MagicModalPortal,
  magicModal,
  useMagicModal,
  MagicModalHideReason
} from "react-native-magic-modal";
import { GestureHandlerRootView } from "react-native-gesture-handler";

type ConfirmationModalReturn = {
  success: boolean;
};

const ConfirmationModal = () => {
  const { hide } = useMagicModal<ConfirmationModalReturn>();

  return (
    <View>
      <TouchableOpacity onPress={() => hide({ success: true })}>
        <Text>Click here to confirm</Text>
      </TouchableOpacity>
    </View>
  );
};

const ResponseModal = ({ text }) => {
  const { hide } = useMagicModal();

  return (
    <View>
      <Text>{text}</Text>
      <TouchableOpacity onPress={() => hide()}>
        <Text>Close</Text>
      </TouchableOpacity>
    </View>
  );
};

const handleConfirmationFlow = async () => {
  // You can call `show` with or without props, depending on the requirements of the modal.
  const result = await magicModal.show<ConfirmationModalReturn>(() => <ConfirmationModal />).promise;

  // Hide could potentially be a backdrop press, a back button press, or a swipe gesture.
  if (result.reason !== MagicModalHideReason.INTENTIONAL_HIDE) {
    // User cancelled the flow
    return;
  }

  if (result.data.success) {
    return magicModal.show(() => <ResponseModal text="Success!" />).promise;
  }

  return magicModal.show(() => <ResponseModal text="Failure :(" />).promise;
};

export const MainScreen = () => {
  return (
    <GestureHandlerRootView>
      <TouchableOpacity onPress={handleConfirmationFlow}>
        <Text>Start the modal flow!</Text>
      </TouchableOpacity>
      <MagicModalPortal />
    </GestureHandlerRootView>
  );
};

You can also hide modals imperatively outside of the modal context. For that, we provide the global hide method, that requires a modal id:

import { magicModal } from "react-native-magic-modal";

const QuickModal = ({ text }) => {
  return (
    <View>
      <Text>Hey! I'm going to be closed imperatively</Text>
    </View>
  );
};

const handleQuickModal = async () => {
  const { modalId } = magicModal.show(QuickModal);

  // Wait for 2 seconds before closing the modal
  await new Promise((resolve) => setTimeout(resolve, 2000));

  // Note that it's usually preferrable to use the `hide` method from the modal context
  // You can even put it inside useEffects to handle auto-dismissal for you.
  magicModal.hide({ modalId });
};

export const MainScreen = () => {
  return (
    <GestureHandlerRootView>
      <TouchableOpacity onPress={handleQuickModal}>
        <Text>Show a quick modal</Text>
      </TouchableOpacity>
      <MagicModalPortal />
    </GestureHandlerRootView>
  );
};

Refer to the kitchen-sink example for detailed usage scenarios.

Documentation

Access the complete documentation here.

FAQ

Q: Can I have two modals showing up at the same time?

A: Yes. With v4+, you can now have multiple modals showing up at the same time.


Q: Can I use Scrollables inside the modal?

A: Yes, but Scrollables can't be used with swipe gestures enabled, as they conflict. Pass in swipeDirection: undefined on the magicModal.show function to disable gestures on them.

If your use-case is a scrollable bottom-sheet, I recommend going with Gorhom's react-native-bottom-sheet for this use-case temporarily.

Contributors

Special thanks to everyone who contributed to making React Native Magic Modal a robust and user-friendly library. See the full list.

See the contributing guide to learn how to contribute to the repository.

License

React Native Magic Modal is licensed under the MIT License.