In our framework (that has angular underneath) we go..
interactionContext.startActivity(MyConfirmDialog).then(result => ...);
...and the interactionContext handles back button clicks and makes sure the dialog is cancelled and our history is kept clean.It even works with nested interactions (i.e. the dialog can start a new interaction in place). Again, the user can click the back button repeatedly to back out of the interaction and everything just works.
I've got no idea how you'd do that in React.
edit - fixed typos
It's simple, you can place a component that provides dialogs near the root of your component tree and provide async functions which open such dialogs to the descendant components (e.g. via contexts). These functions can then be made to resolve only when the dialogs are closed. Thus, all descendant components can use dialogs in the same manner as shown in your example.
The difference between functional and imperative code is there existing more than one such loop available at the same time, and in how your functions are implemented.
A dialog should return a promise. Then you can mix confirmation dialog, fetch calls and so on like this:
Yes, that's imperative code, which violates React principles. But it is insanely simple compared to typical React code.