What Everyone Is Getting Wrong About React Native Modals
Tags: reactnative, opensource, javascript, tutorial
Published: 2022-08-01 17:22:00

![Your codebase on fire as you accept your current modal code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2pqwyggt9c0rjjln9qjp.gif)
**_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](https://redux-saga.js.org/), 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](https://redux-saga.js.org/) that can be called from anywhere. How do you show the modal only when the [Saga’s Action](https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers) is fired? [Sagas](https://redux-saga.js.org/) 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](https://reactjs.org/docs/hooks-reference.html#useimperativehandle) allows you to expose internal properties via Ref. If you have a modal component, you could use [useImperativeHandle](https://reactjs.org/docs/hooks-reference.html#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](https://reactnative.dev/docs/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:

```js
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>
  );
});
```
_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](http://reactjs.org/docs/refs-and-the-dom.html#creating-refs) 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:

```js
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>
  );
};
```

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](https://redux-saga.js.org/).

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](https://github.com/react-native-modal/react-native-modal).

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

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

```js

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>
  );
};
```
_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](https://github.com/react-native-modal/react-native-modal/issues/30)
  
The [React Native Magic Modal](https://github.com/GSTJ/react-native-magic-modal) documentation is easy to understand and will give you a headstart. You can learn more about it here:

{% embed https://github.com/GSTJ/react-native-magic-modal %}

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](https://www.linkedin.com/in/gabrieltaveira/) and [Github](https://github.com/GSTJ)._