DEV Community 👩‍💻👨‍💻

Cover image for Globally control Modals on React Native (using refs)
Royce
Royce

Posted on • Updated on

Globally control Modals on React Native (using refs)

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, 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 in the React Native documentation

2) Add the modalRef then the hook useImperativeHandle and forwardRef()

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);
Enter fullscreen mode Exit fullscreen mode

I use Typescript so if you also do, you can use this type for CustomModalRef

export type CustomModalRef = {
    show: () => void
    hide: () => void
}
Enter fullscreen mode Exit fullscreen mode

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

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

Enter fullscreen mode Exit fullscreen mode

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

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

    useLayoutEffect(() => {
        ModalController.setModalRef(modalRef)
    }, [])

    useImperativeHandle(
    // rest of the code
Enter fullscreen mode Exit fullscreen mode

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 by Kent C. Dodds

5) Declare the component in the top-level component (usually App.tsx)

export default function App() {  
    return <NavigationContainer>
    <MainStack/>
    <CustomModal/>
  </NavigationContainer>
}
Enter fullscreen mode Exit fullscreen mode

6) Use the ModalController class to show/hide the modal
Usage in a component (SomeScreen.js)

/* 
* Component code/logic in between
*/
return (
 <SomeComponent/>
 <Button 
  title="Show modal" 
  onPress={() => {
    ModalController.showModal();
  }} 
 />
)
Enter fullscreen mode Exit fullscreen mode

This is the expected behavior show in the GIF below

Image description

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 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 that will complement my blog called Just Code. 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

Top comments (0)

🤯

"I made 10x faster JSON.stringify() functions, even type safe"

☝️ Must read for JS devs