Skip to content

fix(Modal): prevent double submit on primary button enter key #19151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion packages/react/src/components/Modal/Modal-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright IBM Corp. 2016, 2023
* Copyright IBM Corp. 2016, 2025
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
Expand Down Expand Up @@ -725,4 +725,28 @@ describe('events', () => {
await userEvent.click(secondaryBtn);
expect(onSecondarySubmit).toHaveBeenCalled();
});

it('should not double submit when Enter key is pressed on primary button with `shouldSubmitOnEnter` enabled', async () => {
const { keyboard } = userEvent;
const onRequestSubmit = jest.fn();

render(
<Modal
open
primaryButtonText="Submit"
secondaryButtonText="Cancel"
onRequestSubmit={onRequestSubmit}
shouldSubmitOnEnter>
<p>Test content</p>
</Modal>
);

const primaryButton = screen.getByRole('button', { name: 'Submit' });

primaryButton.focus();
expect(primaryButton).toHaveFocus();

await keyboard('{Enter}');
expect(onRequestSubmit).toHaveBeenCalledTimes(1);
});
});
84 changes: 84 additions & 0 deletions packages/react/src/components/Modal/Modal.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -743,3 +743,87 @@ export const withAILabel = {
);
},
};

export const Test1 = () => {
const [showModal, setShowModal] = useState();

const submit = () => {
console.log('*** i was clicked.');
setShowModal(false);
};

return (
<div>
<Button onClick={() => setShowModal(true)}>Click me to show modal</Button>
<Modal
modalHeading="Double submit bug"
open={showModal}
secondaryButtonText="Cancel"
primaryButtonText="Focus on me and enter"
onRequestSubmit={submit}
danger
shouldSubmitOnEnter>
<div>Submit with enter</div>
</Modal>
</div>
);
};

/**
* Simple state manager for modals.
*/
const ModalStateManager = ({
renderLauncher: LauncherContent,
children: ModalContent,
}) => {
const [open, setOpen] = React.useState(false);

return (
<>
{!ModalContent || typeof document === 'undefined'
? null
: ReactDOM.createPortal(
<ModalContent open={open} setOpen={setOpen} />,
document.body
)}
{LauncherContent && <LauncherContent open={open} setOpen={setOpen} />}
</>
);
};

export const Test2 = () => {
const button = React.useRef();

return (
<ModalStateManager
renderLauncher={({ setOpen }) => (
<Button ref={button} onClick={() => setOpen(true)} kind="danger">
Launch danger modal
</Button>
)}>
{({ open, setOpen }) => (
<Modal
danger
launcherButtonRef={button}
modalHeading="Delete"
primaryButtonText="Delete"
secondaryButtonText="Cancel"
open={open}
onRequestClose={() => setOpen(false)}
shouldSubmitOnEnter
onRequestSubmit={() => console.log('Delete called.')}>
<p style={{ marginBottom: '1rem' }}>
Are you sure you want to delete?
</p>
<TextInput
data-modal-primary-focus
id="text-input-1"
labelText="Confirm deletion"
placeholder="Type 'Confirm' to delete"
style={{ marginBottom: '1rem' }}
/>
</Modal>
)}
</ModalStateManager>
);
};
7 changes: 6 additions & 1 deletion packages/react/src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,10 @@ const Modal = React.forwardRef(function Modal(
}

function handleKeyDown(evt: React.KeyboardEvent<HTMLDivElement>) {
const { target } = evt;

evt.stopPropagation();

if (open) {
if (match(evt, keys.Escape)) {
onRequestClose(evt);
Expand All @@ -308,7 +311,9 @@ const Modal = React.forwardRef(function Modal(
if (
match(evt, keys.Enter) &&
shouldSubmitOnEnter &&
!isCloseButton(evt.target as Element)
target instanceof Element &&
!isCloseButton(target) &&
document.activeElement !== button.current
) {
onRequestSubmit(evt);
}
Expand Down
Loading