DEV Community

Rex
Rex

Posted on • Updated on

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

  1. DialogOptions now extends material-ui DialogProps so that all Dialog props are available to our createDialog function. This is powerful because now we customise our modal as if we are working with material-ui's Dialog component directly. Note that because of this change, our closeDialog is only responsible for removing the last modal, no more worry about modal events like onClose.

  2. DialogContainer now takes a title and render a title bar a close icon button.

Problem with asynchronous state

As we know, React state is asynchronous, i.e. we would not get the lastest 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.

  1. It takes a callback function.

  2. It maintains a call state and initialised to false.

  3. It calls the given callback in a useEffect block whenever the call state is true. The call is then restored to false right after the call.

  4. The hook returns a function. Calling the function would set the call flag to true and consequently calling whatever is given as callback.

I would love to know a better way to do it, and please share if you have one.

Happy coding…

Discussion (0)