Skip to content

Commit f57dd3d

Browse files
authored
fix(SelectPanel2): only bind keydown event when necessary (#4601)
* fix(SelectPanel2): only bind keydown event when necessary * chore(SelectPanel2): Add changeset
1 parent 9b63299 commit f57dd3d

File tree

3 files changed

+40
-3
lines changed

3 files changed

+40
-3
lines changed

.changeset/good-bugs-grab.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react': patch
3+
---
4+
5+
SelectPanel2: Minor optimization for escape key event listener binding

packages/react/src/drafts/SelectPanel2/SelectPanel.test.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,38 @@ describe('SelectPanel', () => {
179179
expect(mockOnSubmit).toHaveBeenCalledTimes(0)
180180
})
181181

182+
it('should not call addEventListener on each render for Escape key handling when onCancel has not changed', async () => {
183+
const onCancel = jest.fn()
184+
const container = render(
185+
<SelectPanel title="title" onCancel={onCancel}>
186+
child
187+
</SelectPanel>,
188+
)
189+
const addEventListenerSpy = jest.spyOn(globalThis.EventTarget.prototype, 'addEventListener')
190+
const removeEventListenerSpy = jest.spyOn(globalThis.EventTarget.prototype, 'removeEventListener')
191+
192+
container.rerender(
193+
<SelectPanel title="title" onCancel={onCancel}>
194+
child
195+
</SelectPanel>,
196+
)
197+
expect(addEventListenerSpy).not.toHaveBeenCalled()
198+
expect(removeEventListenerSpy).not.toHaveBeenCalled()
199+
})
200+
201+
it('Escape key closes the dialog and calls onCancel', async () => {
202+
const mockOnSubmit = jest.fn()
203+
const mockOnCancel = jest.fn()
204+
const {container, user} = await getFixtureWithOpenContainer({mockOnSubmit, mockOnCancel})
205+
selectUnselectedOption(container, user)
206+
207+
await user.keyboard('{Escape}')
208+
209+
expect(container.queryByRole('dialog')).toBeNull()
210+
expect(mockOnCancel).toHaveBeenCalledTimes(1)
211+
expect(mockOnSubmit).toHaveBeenCalledTimes(0)
212+
})
213+
182214
it('SelectPanel within FormControl should be labelled by FormControl.Label', async () => {
183215
const component = render(<SelectPanelWithinForm />)
184216
const buttonByRole = component.getByRole('button')

packages/react/src/drafts/SelectPanel2/SelectPanel.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,10 @@ const Panel: React.FC<SelectPanelProps> = ({
136136
if (propsOpen === undefined) setInternalOpen(false)
137137
}, [internalOpen, propsOpen])
138138

139-
const onInternalCancel = () => {
139+
const onInternalCancel = React.useCallback(() => {
140140
onInternalClose()
141141
if (typeof propsOnCancel === 'function') propsOnCancel()
142-
}
142+
}, [onInternalClose, propsOnCancel])
143143

144144
const onInternalSubmit = (event?: React.FormEvent<HTMLFormElement>) => {
145145
event?.preventDefault() // there is no event with selectionVariant=instant
@@ -199,7 +199,7 @@ const Panel: React.FC<SelectPanelProps> = ({
199199
}
200200
dialogEl?.addEventListener('keydown', handler)
201201
return () => dialogEl?.removeEventListener('keydown', handler)
202-
})
202+
}, [onInternalCancel])
203203

204204
// Autofocus hack: React doesn't support autoFocus for dialog: https://github.com/facebook/react/issues/23301
205205
// tl;dr: react takes over autofocus instead of letting the browser handle it,

0 commit comments

Comments
 (0)