Material-ui Dialog Provider — Render modal on-demand and deal with Asynchronous State

To use Material-ui dialog
component, the official way is to add the dialog
component to the component where you would open or close it by setting a open
flag to true
or false
. The problem with this approach is that React will always render the modal. If the user never opens the modal, it is a waste. This is not a problem we should worry about until we have rarely used and expensive modal. For example, a modal uses a hook to get a large list of data and requires some computation. The computation will take place every time, even if the modal is never opened. This is a good use case for a Dialog Provider to facilitate rendering modal on demand.
I have adapted this awesome solution by NearHuscarl from StackOverflow, with some tweaks to meet my needs better. I also have to come up with a solution to resolve stale states in modals created on-demand. Code is shown in below CodeSandbox.
Tweaks
DialogOptions
now extendsmaterial-ui
DialogProps
so that allDialog
props are available to ourcreateDialog
function. This is powerful because now we customise our modal as if we are working with material-ui’sDialog
component directly. Note that because of this change, ourcloseDialog
is only responsible for setting the modal flag tofalse
, no more worry about modal events likeonClose
.DialogContainer
now takes atitle
and render a title bar aclose
icon button.
Problem with asynchronous state
As we know, React state is asynchronous, i.e. we would not get the latest state right after we set it. We will have it in `useEffect` or the next render circle. For example, if we setState('new value')
then console.log(state)
right after, what gets printed in the console will not be “new value” but the previous stale state. If we console log the state in useEffect
, we will have the current state.
This is a problem for our modal because if we use states in our modal, when we set the states and launch our modal right after, our modal would not have the latest state because it is created and rendered in the same render circle. For a demonstration, refresh and click the “Without Hook” button in CodeSandbox. One way to resolve the problem is that we delay and launch the modal in useEffect
.
I have implemented a hook called useRunAsEffect
for the above problem.
- It takes a
callback
function. - It maintains a
call
state and initialised tofalse.
- It calls the given callback in a
useEffect
block whenever thecall
state istrue
. Thecall
is then restored tofalse
right after the call. - It returns a function. Calling the function would set the
call
flag totrue
and consequently calling whatever is given ascallback
.
I would love to know a better way to do it, and please share if you have one.
Happy coding…