Skip to content

Commit 7eee3a8

Browse files
authored
fix(Modal): prevent double submit on primary button enter key (#19151)
1 parent 0ce91ed commit 7eee3a8

File tree

3 files changed

+115
-2
lines changed

3 files changed

+115
-2
lines changed

packages/react/src/components/Modal/Modal-test.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -725,4 +725,28 @@ describe('events', () => {
725725
await userEvent.click(secondaryBtn);
726726
expect(onSecondarySubmit).toHaveBeenCalled();
727727
});
728+
729+
it('should not double submit when Enter key is pressed on primary button with `shouldSubmitOnEnter` enabled', async () => {
730+
const { keyboard } = userEvent;
731+
const onRequestSubmit = jest.fn();
732+
733+
render(
734+
<Modal
735+
open
736+
primaryButtonText="Submit"
737+
secondaryButtonText="Cancel"
738+
onRequestSubmit={onRequestSubmit}
739+
shouldSubmitOnEnter>
740+
<p>Test content</p>
741+
</Modal>
742+
);
743+
744+
const primaryButton = screen.getByRole('button', { name: 'Submit' });
745+
746+
primaryButton.focus();
747+
expect(primaryButton).toHaveFocus();
748+
749+
await keyboard('{Enter}');
750+
expect(onRequestSubmit).toHaveBeenCalledTimes(1);
751+
});
728752
});

packages/react/src/components/Modal/Modal.stories.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,3 +743,87 @@ export const withAILabel = {
743743
);
744744
},
745745
};
746+
747+
export const Test1 = () => {
748+
const [showModal, setShowModal] = useState();
749+
750+
const submit = () => {
751+
console.log('*** i was clicked.');
752+
setShowModal(false);
753+
};
754+
755+
return (
756+
<div>
757+
<Button onClick={() => setShowModal(true)}>Click me to show modal</Button>
758+
<Modal
759+
modalHeading="Double submit bug"
760+
open={showModal}
761+
secondaryButtonText="Cancel"
762+
primaryButtonText="Focus on me and enter"
763+
onRequestSubmit={submit}
764+
danger
765+
shouldSubmitOnEnter>
766+
<div>Submit with enter</div>
767+
</Modal>
768+
</div>
769+
);
770+
};
771+
772+
/**
773+
* Simple state manager for modals.
774+
*/
775+
const ModalStateManager = ({
776+
renderLauncher: LauncherContent,
777+
children: ModalContent,
778+
}) => {
779+
const [open, setOpen] = React.useState(false);
780+
781+
return (
782+
<>
783+
{!ModalContent || typeof document === 'undefined'
784+
? null
785+
: ReactDOM.createPortal(
786+
<ModalContent open={open} setOpen={setOpen} />,
787+
document.body
788+
)}
789+
{LauncherContent && <LauncherContent open={open} setOpen={setOpen} />}
790+
</>
791+
);
792+
};
793+
794+
export const Test2 = () => {
795+
const button = React.useRef();
796+
797+
return (
798+
<ModalStateManager
799+
renderLauncher={({ setOpen }) => (
800+
<Button ref={button} onClick={() => setOpen(true)} kind="danger">
801+
Launch danger modal
802+
</Button>
803+
)}>
804+
{({ open, setOpen }) => (
805+
<Modal
806+
danger
807+
launcherButtonRef={button}
808+
modalHeading="Delete"
809+
primaryButtonText="Delete"
810+
secondaryButtonText="Cancel"
811+
open={open}
812+
onRequestClose={() => setOpen(false)}
813+
shouldSubmitOnEnter
814+
onRequestSubmit={() => console.log('Delete called.')}>
815+
<p style={{ marginBottom: '1rem' }}>
816+
Are you sure you want to delete?
817+
</p>
818+
<TextInput
819+
data-modal-primary-focus
820+
id="text-input-1"
821+
labelText="Confirm deletion"
822+
placeholder="Type 'Confirm' to delete"
823+
style={{ marginBottom: '1rem' }}
824+
/>
825+
</Modal>
826+
)}
827+
</ModalStateManager>
828+
);
829+
};

packages/react/src/components/Modal/Modal.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,10 @@ const Modal = React.forwardRef(function Modal(
309309
}
310310

311311
function handleKeyDown(evt: React.KeyboardEvent<HTMLDivElement>) {
312+
const { target } = evt;
313+
312314
evt.stopPropagation();
315+
313316
if (open) {
314317
if (match(evt, keys.Escape)) {
315318
onRequestClose(evt);
@@ -318,7 +321,9 @@ const Modal = React.forwardRef(function Modal(
318321
if (
319322
match(evt, keys.Enter) &&
320323
shouldSubmitOnEnter &&
321-
!isCloseButton(evt.target as Element)
324+
target instanceof Element &&
325+
!isCloseButton(target) &&
326+
document.activeElement !== button.current
322327
) {
323328
onRequestSubmit(evt);
324329
}

0 commit comments

Comments
 (0)