Globally control Modals on React Native (using refs)
Tags: reactnative, javascript, modals
Published: 2022-09-26 01:05:21

This article will demonstrate a simple example of how you can launch React Native modals using refs from any component instead of passing states and context data. 

## Why?
From the official React Native documentation regarding [Modals](https://reactnative.dev/docs/modal), we know that we can control modal visibility content and data using states and passing data through props but we cannot directly pass refs to the RN Modal component. This means that for larger and more complex projects, if you want to control a modal at a global level you can do some of these things:
- Passing props through components (a lot of components)
- Implement modal state management through context and reducers
- Use a third-party package that can handle that (which is probably limited in terms of providing ways to customize default behavior and UI)

The question now is what if we could implement a way so that we only have to make a custom modal component once and not resort to complex prop passing or use context?

A simple **.showModal(data)** method call from anywhere across different components would be perfect and the topic this blog will share.

## Some Disclaimers

This probably is not for people new to React Native, but still a great opener to the idea of implementing custom functionality with refs. The contents of this blog is better applied to a larger project that will use the same modal in multiple components. If you're app is simple, it's probably better to stick to simpler implementations.

## The implementation
1) Create the Custom Modal 

I modified the raw [example here](https://reactnative.dev/docs/modal) in the React Native documentation

2) Add the modalRef then the hook **useImperativeHandle** and **forwardRef()** 
``` jsx
const CustomModal = () => {
	const [modalVisible, setModalVisible] = useState(false);
    const modalRef = useRef<CustomModalRef>();

	useImperativeHandle(
		modalRef,
		() => ({
			show: () => {
				setModalVisible(true);
			},
			hide: () => {
				setModalVisible(false);
			},
		}),
		[]
	);
   // rest of your code
}

export default forwardRef(CustomModal);
```
I use Typescript so if you also do, you can use this type for  **CustomModalRef**

``` jsx
export type CustomModalRef = {
    show: () => void
    hide: () => void
}
```

3) Create a Modal Controller class that will use static variables and functions for managing the modal refs

``` jsx
import { MutableRefObject } from "react"

export type CustomModalRef = {
    show: () => void
    hide: () => void
}

export default class ModalController { 
    static modalRef: MutableRefObject<CustomModalRef>;
    static setModalRef = (ref: any) => {
        this.modalRef = ref
    }

    static showModal = () => {
        this.modalRef.current?.show()
    }

    static hideModal = () => {
        this.modalRef.current?.hide()
    }
}

```

The **showModal** and **hideModal** static methods can be optional, you can directly call **current.show** but I chose to create the methods to provide more custom functionality 

4) Assign the modal ref in Custom Modal to the static variable in the Modal Controller class

``` jsx
const CustomModal = () => {
	const [modalVisible, setModalVisible] = useState(false);
    const modalRef = useRef<CustomModalRef>();

    useLayoutEffect(() => {
        ModalController.setModalRef(modalRef)
    }, [])
    
	useImperativeHandle(
    // rest of the code
```
I used the **useLayoutEffect** since I'm updating a ref value. You can read about the differences of **useEffect** and **useLayoutEffect** you can check this great [blog](https://kentcdodds.com/blog/useeffect-vs-uselayouteffect) by Kent C. Dodds 

5) Declare the component in the top-level component (usually **App.tsx**)
``` jsx
export default function App() {  
	return <NavigationContainer>
    <MainStack/>
    <CustomModal/>
  </NavigationContainer>
}
```
6) Use the ModalController class to show/hide the modal
**Usage in a component (SomeScreen.js)**
``` jsx
/* 
* Component code/logic in between
*/
return (
 <SomeComponent/>
 <Button 
  title="Show modal" 
  onPress={() => {
    ModalController.showModal();
  }} 
 />
)
```

This is the expected behavior show in the GIF below

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3dyhvclw7ldthab3aata.gif)

## Recommendations

You can now extend this concept to accept data like a string message that makes a really flexible global modal that you can easily call without adding a new 3rd party library or a  context provider setup made specifically to manage modals. You can check this [Github repository](https://github.com/roycechua/rn-global-modal-control-example) I made that shows this exact example but also with the added **message** parameter. You can clone this repo and checkout from the **boilerplate** branch.

Thanks for taking the time to read this, hope it helped!

Your support would be very much appreciated. Buying me a coffee would mean a lot
https://www.buymeacoffee.com/royce.chua

This blog will probably have a video posted on Youtube soon. I'm putting together a [Youtube channel](https://www.youtube.com/channel/UCVCAGgg9Gs9RgENKurnI6bQ) that will complement my blog called [Just Code](https://www.youtube.com/channel/UCVCAGgg9Gs9RgENKurnI6bQ). Please visit for some content soon.

## Some additional remarks

I forgot to mention here as a reminder that the implementation of the React Native modal does not allow multiple modals (unrelated to the contents of this blog). You must still make sure that you close the first one before opening the second modal using setTimeout(). This is also discussed in the react-native-modal library issues
[https://github.com/react-native-modal/react-native-modal/issues/30](https://github.com/react-native-modal/react-native-modal/issues/30)