paint-brush
What Everyone Is Getting Wrong About React Native Modalsby@gabrieltaveira
376 reads
376 reads

What Everyone Is Getting Wrong About React Native Modals

by Gabriel TaveiraAugust 2nd, 2022
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Do you find using modals in React Native to be a bit of a pain? This article teaches you how to get a better development experience with them.

Company Mentioned

Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - What Everyone Is Getting Wrong About React Native Modals
Gabriel Taveira HackerNoon profile picture


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](https://(https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers) 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>
  );
});

That's the transition to a better place.


While this is a step in the right direction, it doesn't solve all of our problems. 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.


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

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 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


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.


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

https://github.com/GSTJ/react-native-magic-modal


Contributions accepted!


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


Also Published here