Taking advantage of Redux store and creating globally controlled modals.
Usually tutorials that you find about creating modals for React are the simplest ones and actually they are not used alone in the big apps. In fact whole reason of existence of Redux is to able to make complicated apps/flows without getting lost in the component tree.
So, lets see anatomy of classic modal examples:
As you see in the pen, App component is in charge of controlling opened/closed state of the simple modal. Some seniors still prefer this approach in order to have full ability to control modal component and its props locally. But this is not the only way to initiate a modal.
Advantages of component state approach:
- ability to fully control modal component.
Disadvantages of component state approach:
- You can’t control this modal from another component.
- In order to interfere this modal component, you will need to pass props to App component that in charge.
- Modal can’t close itself, you need to pass closing prop to modal.
- If your parent component has overflow hidden property, it will be hard for you to show a modal as a child.
- If you may need multiple modals to be opened in some case, you need to hold another state and create another modal component to show it.
This multiple modal case seems to be edge case but let me illustrate: You opened a modal to show something to user and there is a another modal initiated by this modal. And maybe your app is real time and it has a socket connection.Design suggests that if socket connection drops, you need to show a another modal with a reconnecting indicator. In that case, you have to deal with layers and z-indexes of these modals. This z-index numbers should be consistent and work properly with the modals, indicators, notifications in the rest of the app.In addition to these reasons, modal/dialog structures usually are the same and probably most of the apps will not need custom local modals in most cases.What is the solution then?
If you already implemented Redux in your app, adding one property to global reducer to control all the modals will solve the problem.
Advantages of Redux powered modals:
- You will be able to fire modals anywhere in your app just by dispatching one action.
- You will be able to close any modal from anywhere(imagine that you may need to close a modal as a result of async action in another component)
- z-indexes will be handled automatically in case of multiple modals
- Modal will be able to close itself. You don’t need to handle close function when you dispatch modal.
- You can add a whole custom component to render in dispatching payload
Disadvantages of Redux powered modals:
- You need to have dispatch function and import related action (connect the component or pass dispatch function as a prop)
- If you need to fire specific modals like
Confirmation Modalyou need to add this to Modal initiator as a different condition or feed the payload of dispatching function with that custom modal.
Let’s see what redux powered modals looks like:
In that pen, you will notice that one <ModalContainer /> in the root App container is rendering all the modals.This modals will be in the bottom of top level component, so probably you will not need to control z-index but still i added increment anyway. In reducer, modal property is array and i am populating it. You can fire multiple modals and you will see the opacity changes of modal layers. `uuid` is for to identify modal in the array to be able to close it or interact with it. You can attach function to closing event if you need it and modal closes itself.
This approach solves many problems but some people want to put modals outside of root element of React or simply inside of <body> as a different element just like we did with jquery when we bootstraping the modals. You may see the the usage of
ref and creating dom element in React related sources. We tried to initiate modal like this:
But this usage created lots of problems because when we remove the ref of the dom node, we are not actually removing or unmounting the component. And component will not have proper life-cycle. In addition, this component will be created over and over again. This usage may cause memory leak and performance issues.
You see, there is no unmounting in the console logs and each time i open and close a modal, <Modal> component is created but not removed.
In order to make this work, component should remove its own dom node in the componentWillUnmount.
Solution came with the React 16: Portals
Portals - React
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the…
Here is the modal example of Portals (reactjs.org):
You see modal is used like a wrapper here and depend on component state. This is similar to first pen in this article and only difference is rendering location of the modal.
Let’s see what will happen when we implement redux control to this approach:
This `<MyPortal>` component can be use the show tooltips, notifications and Ui elements.
Here is the Github repo if you want to tweak around:
react-redux-modals - This repo created for Medium.com: React/Redux: Modals and Dialogs
As of this month(December 2017), this seems to be best solution for common modal usage. I will update this article when i see better approaches.
Follow me on twitter: