Version number
1.14.0
Description of the bug
When using Dialog as a controlled component (passing open as a boolean prop), setting open={false} from outside the dialog triggers onClose. The component internally calls dialog.close() to sync the native DOM element when the prop changes, which fires the native close event, which is then forwarded to onClose.
This violates the expected controlled component contract: onClose should only fire in response to a user gesture (backdrop click, close button, Escape key). It must not fire when the parent drives the close by changing the open prop. Analogously, a controlled <input> does not fire onChange when its value prop is updated externally.
Steps To Reproduce
-
Mount a Dialog with a controlled open prop:
const [open, setOpen] = useState(true);
<Dialog
open={open}
onClose={() => console.log("onClose fired")}
>
<Button onClick={() => setOpen(false)}>Save</Button>
</Dialog>
-
Click Save — this sets open=false via React state, bypassing any dialog close mechanism entirely.
-
Observe the console — "onClose fired" is logged even though the user did not interact with a close gesture on the dialog.
Additional Information
Expected Behavior
onClose fires only when the user triggers a close gesture on the dialog itself (backdrop click, close button, Escape key). Setting open={false} via props must be silent — it is the parent acknowledging the close, not initiating a new one.
Workaround
Deduplicate with a ref guard in the consuming code until this is fixed:
const firedRef = useRef(false);
if (open) firedRef.current = false;
const handleClose = () => {
if (firedRef.current) return;
firedRef.current = true;
setOpen(false);
};
<Dialog open={open} onClose={handleClose}>
...
</Dialog>
Version number
1.14.0
Description of the bug
When using
Dialogas a controlled component (passingopenas a boolean prop), settingopen={false}from outside the dialog triggersonClose. The component internally callsdialog.close()to sync the native DOM element when the prop changes, which fires the nativecloseevent, which is then forwarded toonClose.This violates the expected controlled component contract:
onCloseshould only fire in response to a user gesture (backdrop click, close button, Escape key). It must not fire when the parent drives the close by changing theopenprop. Analogously, a controlled<input>does not fireonChangewhen itsvalueprop is updated externally.Steps To Reproduce
Mount a
Dialogwith a controlledopenprop:Click Save — this sets
open=falsevia React state, bypassing any dialog close mechanism entirely.Observe the console —
"onClose fired"is logged even though the user did not interact with a close gesture on the dialog.Additional Information
Expected Behavior
onClosefires only when the user triggers a close gesture on the dialog itself (backdrop click, close button, Escape key). Settingopen={false}via props must be silent — it is the parent acknowledging the close, not initiating a new one.Workaround
Deduplicate with a ref guard in the consuming code until this is fixed: