fix(mui): freeze loading state + button label during dialog close (Modal + ConfirmDialog)
On save/delete success the parent flips loading false a beat before the dialog finishes its fade-out, so the button flashed back to 'Uložit změny'/'Smazat'. Both dialogs now freeze the loading flag (and the label props) while open, so the closing dialog keeps showing 'Ukládám…'/'Pracuji…'. Same derive-state-from-props pattern as the title freeze. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,21 +32,27 @@ export default function ConfirmDialog({
|
|||||||
loading = false,
|
loading = false,
|
||||||
}: ConfirmDialogProps) {
|
}: ConfirmDialogProps) {
|
||||||
useDialogScrollLock(isOpen);
|
useDialogScrollLock(isOpen);
|
||||||
// Freeze the shown title/message while closing, so the fade-out transition
|
// Freeze content + label + loading while open, so neither the message NOR the
|
||||||
// never flashes empty when the caller clears its source data (e.g. sets the
|
// confirm button label flips during the close fade-out (e.g. on confirm,
|
||||||
// selected row to null) at the same instant it closes the dialog. This is
|
// `loading` goes false a beat before the dialog finishes closing — without
|
||||||
// React's "adjust state from props during render" pattern: update only while
|
// this the button would flash back to "Smazat"/confirmText). React
|
||||||
// open, keep the last values once closed. (Self-contained — callers can clear
|
// "adjust state from props during render": update only while open. Also keeps
|
||||||
// their data on close however they like.)
|
// content from flashing empty when the caller clears its row on close.
|
||||||
const [shown, setShown] = useState({ title, message });
|
const [shown, setShown] = useState({ title, message, confirmText, loading });
|
||||||
if (isOpen && (shown.title !== title || shown.message !== message)) {
|
if (
|
||||||
setShown({ title, message });
|
isOpen &&
|
||||||
|
(shown.title !== title ||
|
||||||
|
shown.message !== message ||
|
||||||
|
shown.confirmText !== confirmText ||
|
||||||
|
shown.loading !== loading)
|
||||||
|
) {
|
||||||
|
setShown({ title, message, confirmText, loading });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
onClose={loading ? undefined : onClose}
|
onClose={shown.loading ? undefined : onClose}
|
||||||
disableScrollLock
|
disableScrollLock
|
||||||
slotProps={{ paper: { sx: { borderRadius: 3 } } }}
|
slotProps={{ paper: { sx: { borderRadius: 3 } } }}
|
||||||
>
|
>
|
||||||
@@ -55,16 +61,16 @@ export default function ConfirmDialog({
|
|||||||
<DialogContentText>{shown.message}</DialogContentText>
|
<DialogContentText>{shown.message}</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||||
<MuiButton onClick={onClose} color="inherit" disabled={loading}>
|
<MuiButton onClick={onClose} color="inherit" disabled={shown.loading}>
|
||||||
{cancelText}
|
{cancelText}
|
||||||
</MuiButton>
|
</MuiButton>
|
||||||
<MuiButton
|
<MuiButton
|
||||||
onClick={onConfirm}
|
onClick={onConfirm}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color={confirmVariant === "danger" ? "error" : "primary"}
|
color={confirmVariant === "danger" ? "error" : "primary"}
|
||||||
disabled={loading}
|
disabled={shown.loading}
|
||||||
>
|
>
|
||||||
{loading ? "Pracuji…" : confirmText}
|
{shown.loading ? "Pracuji…" : shown.confirmText}
|
||||||
</MuiButton>
|
</MuiButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -39,20 +39,25 @@ export default function Modal({
|
|||||||
// Freeze the shown title/subtitle/submitText while closing, so the fade-out
|
// Freeze the shown title/subtitle/submitText while closing, so the fade-out
|
||||||
// never flashes the "create" variant when the caller clears `editingUser` on
|
// never flashes the "create" variant when the caller clears `editingUser` on
|
||||||
// close (React derive-state-from-props: update only while open).
|
// close (React derive-state-from-props: update only while open).
|
||||||
const [shown, setShown] = useState({ title, subtitle, submitText });
|
// Freeze label-affecting props (incl. `loading`) while open, so neither the
|
||||||
|
// title/submitText NOR the loading state flips during the close fade-out
|
||||||
|
// (e.g. on save, `loading` goes false a beat before the dialog finishes
|
||||||
|
// closing — without this the button would flash back to "Uložit změny").
|
||||||
|
const [shown, setShown] = useState({ title, subtitle, submitText, loading });
|
||||||
if (
|
if (
|
||||||
isOpen &&
|
isOpen &&
|
||||||
(shown.title !== title ||
|
(shown.title !== title ||
|
||||||
shown.subtitle !== subtitle ||
|
shown.subtitle !== subtitle ||
|
||||||
shown.submitText !== submitText)
|
shown.submitText !== submitText ||
|
||||||
|
shown.loading !== loading)
|
||||||
) {
|
) {
|
||||||
setShown({ title, subtitle, submitText });
|
setShown({ title, subtitle, submitText, loading });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
onClose={loading ? undefined : onClose}
|
onClose={shown.loading ? undefined : onClose}
|
||||||
fullWidth
|
fullWidth
|
||||||
maxWidth={maxWidth}
|
maxWidth={maxWidth}
|
||||||
disableScrollLock
|
disableScrollLock
|
||||||
@@ -68,15 +73,15 @@ export default function Modal({
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>{children}</DialogContent>
|
<DialogContent>{children}</DialogContent>
|
||||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||||
<MuiButton onClick={onClose} color="inherit" disabled={loading}>
|
<MuiButton onClick={onClose} color="inherit" disabled={shown.loading}>
|
||||||
{cancelText}
|
{cancelText}
|
||||||
</MuiButton>
|
</MuiButton>
|
||||||
<MuiButton
|
<MuiButton
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={loading || submitDisabled}
|
disabled={shown.loading || submitDisabled}
|
||||||
>
|
>
|
||||||
{loading ? "Ukládám…" : shown.submitText}
|
{shown.loading ? "Ukládám…" : shown.submitText}
|
||||||
</MuiButton>
|
</MuiButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
Reference in New Issue
Block a user