DEV Community

Cover image for What Everyone Is Getting Wrong About React Native Modals
Gabriel Taveira
Gabriel Taveira

Posted on • Updated on • Originally published at Medium

What Everyone Is Getting Wrong About React Native Modals

Your codebase on fire as you accept your current modal code
A guide on how to master React Native modal complex flows.

Do you find using modals in React Native to be a bit of a pain? You’re not alone! Trying to keep control of its open state and repeating the code everywhere you want to use it can be pretty tedious.

And the problem only gets worse when you try to create complex flows. Once you get past two modals, your main component is a mess, and the state is all over the place.

I’ve experienced this first-hand in many big companies, like Zé Delivery (by AB-InBev), Alfred Delivery, and now at X-Team.

But don’t despair! Most people think that’s the only way. Still, in this article, I will explain how problems start and how to deal with them elegantly while improving your development experience. It will even help you use modals inside and outside React components (in Sagas, for example!)

This article's teachings have been encapsulated in this library:
https://github.com/GSTJ/react-native-magic-modal.

The Challenges Of A “Simple Flow”

Imagine this. You work on Facebook, and the Product Team asks for a ‘simple flow’:

As a company, I would like to show a modal asking the user to rate the app between 0–5 stars after the user likes a post for the first time.

a. If the user rates it with less than four stars, show another modal asking for feedback on how to improve.
b. Otherwise, show a happy modal asking the user to rate us on the app store.

Finally, show a ‘thanks’ modal, thanking the user for their support.

It isn’t so far-fetched, right?

Four modals deep, it brings up a lot of questions from the developer's side. Where should this logic be placed? How should we process the output from the last modal? How do we keep this clean?

Can you spot what the code will be like? Do you see yourself writing complex logic to handle the order of the modals, making sure every modal has already transitioned to not visible and having tons of useStates in place?

The growth of the problem

Imagine you finally did it, you finished the flow, and the Product Team loves the outcome! They now want to expand this flow to show up only once whenever the user likes anything for the first time, whether it’s a post, a comment, or a product. How would you approach it?

You will have to copy-paste all those four modals on every screen where liking is possible. Maybe you even go ahead and turn all of those into a single component. Even then, adding this wrapper component to every screen is still needed.

A few months pass by, and the Development Team now sees the complexity of handling likes differently on every screen and wants to pass this responsibility to a Redux Saga that can be called from anywhere. How do you show the modal only when the Saga’s Action is fired? Sagas run outside React components.

I can confidently say that I’ve experienced those scenarios happening. Working in the food delivery industry, we consistently asked the user to rate the app, the delivery, and their purchase.

How to avoid it?

Let’s start by tackling the state. Managing it is one of the most important things while working with modals.

Expose Internal Properties With “useImperativeHandle”

In short, useImperativeHandle allows you to expose internal properties via Ref. If you have a modal component, you could use useImperativeHandle to expose their show and hide functions. Meaning it can take care of its own state without delegating to its parent component with props, for example.

This can be helpful when you want to make your code clearer and avoid passing down many props. Let’s give it a try:

import React, { useState, useImperativeHandle } from 'react';
import { Text } from 'react-native';
import ModalContainer from 'react-native-modal';

export const ExampleModal = React.forwardRef((ref) => {
  const [isVisible, setIsVisible] = useState(false);

  const show = () => setIsVisible(true);
  const hide = () => setIsVisible(false);

  useImperativeHandle(ref, () => ({ hide, show }));

  return (
    <ModalContainer onBackdropPress={hide} isVisible={isVisible}>
      <Text>My awesome modal!</Text>
    </ModalContainer>
  );
});
Enter fullscreen mode Exit fullscreen mode

That's the transition to a better place.

While a step in the right direction, it doesn’t solve all of our problems by itself. Namely:

  • To use it, we need to pass a ref prop from a useRef hook, which means we can't call show outside React components.
  • We still need to instantiate the component on every screen. There’s no way to use it on multiple screens without having ExampleModal repeated.

Externally exposing the component’s Ref

Most people don't know this, but React has a createRef method that can be used outside React components. In fact, it's even in the React-Navigation documentation for edge cases.

https://reactnavigation.org/docs/navigating-without-navigation-prop/

In practice, the flexibility it brings can be seen here:

import React, { useState, useImperativeHandle } from 'react';
import { Text } from 'react-native';
import ModalContainer from 'react-native-modal';

export const imperativeModalRef = React.createRef();

export const SmartExample = () => {
  const [isVisible, setIsVisible] = useState(false);

  const show = () => setIsVisible(true);
  const hide = () => setIsVisible(false);

  useImperativeHandle(imperativeModalRef, () => ({ hide, show }));

  return (
    <ModalContainer onBackdropPress={hide} isVisible={isVisible}>
      <Text>My awesome modal!</Text>
    </ModalContainer>
  );
};
Enter fullscreen mode Exit fullscreen mode

Now imperativeModalRef can be imported and used anywhere, as long as SmartExample is on the root. We can use show and hide from every component, function, or Saga.

That solves most of our issues, but there's still one: It is hard to manage a project with many modal refs and modals on the root.

That's where you can get creative with abstractions to make the SmartExample render any modal you want! One way to do this is to make the show function receive a component and render it.

Going the extra mile

Instead of making you guys reinvent the wheel, I've created an open-source library that encapsulates all these concepts and more, with full TypeScript support based on react-native-modal.

Here's a basic idea of how to use it:

"Talk is cheap. Show me the code." ― Linus Torvalds.


import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { MagicModalPortal, magicModal } from 'react-native-magic-modal';

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

const ResponseModal = ({ text }) => (
  <View>
    <Text>{text}</Text>
    <TouchableOpacity onPress={() => magicModal.hide()}>
      <Text>Close</Text>
    </TouchableOpacity>
  </View>
);

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

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

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

export const MainScreen = () => {
  return (
    <View>
      <TouchableOpacity onPress={handleConfirmationFlow}>
        <Text>Start the modal flow!</Text>
      </TouchableOpacity>
      <MagicModalPortal />
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

Example using react-native-magic-modal

As you can see, it gives you loads of flexibility, unimaginable before, by simply abstracting the concepts we’ve gone through.

Now, the confirmation flow can be called from anywhere. Inside or outside React components.

Also, it automatically deals with common issues regarding modals.

Did you know that, in React Native, you can’t show two modals simultaneously?

Even if you try to show a modal right after another, it would probably fail as the last modal is still animating its ‘close’ state. Fortunately, the issue is already dealt with on our side.

https://github.com/react-native-modal/react-native-modal/issues/30

The React Native Magic Modal documentation is easy to understand and will give you a headstart. You can learn more about it here:

GitHub logo GSTJ / react-native-magic-modal

🦄 A modal library that can be called imperatively from anywhere!

MagicModal A modal library that can be called imperatively from anywhere!

React Native Magic Modal 🦄

Do you find using modals in React Native to be a bit of a pain? Trying to keep control of its open state and repeating the code everywhere you want to use it can be pretty tedious.

And the problem only gets worse when you try to create complex flows, where one modal opens another with conditionals in place. Once you get past two modals, your main component is a mess, and the state is all over the place.

This library thoughtfully encapsulates complex concepts to provide a smooth experience when using React modals, inside or outside components (In Sagas, for example!)

Take a look to a in-depth explanation of its concepts on its Medium article.

📸 Examples

IOS Android

🛠 Installation

yarn add react-native-magic-modal
Enter fullscreen mode Exit fullscreen mode

⚙️ Usage

First, insert a MagicModalPortal in the…

This same logic has already been battle-tested on big companies I’ve worked on, like Zé Delivery (by AB-InBev), Alfred Delivery, and X-Team.

Contributions accepted!

Thanks for the read. If you liked the article, follow me on Linkedin and Github.

Latest comments (0)