Skip to content

Commit 4b505e8

Browse files
authored
Fix incorrect double invocation of menu items (#3766)
This PR fixes an issue where menu items would be invoked twice when you click on an item. This is due to a bug in the `useQuickRelease` hook where the timestamp was not reset correctly, which means that the actual click caused a quick release trigger. This in turn selects / invokes the menu item even though it was already triggered by the click of the user. This PR fixes that by resetting the timestamp as soon as possible, which also cleans up the code a bit because there is only 1 branch where we need to reset instead of in every branch before returning. Fixes: #3763 Fixes: #3764 Fixes: #3765
1 parent 51b5bf6 commit 4b505e8

File tree

2 files changed

+10
-8
lines changed

2 files changed

+10
-8
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
- Nothing yet!
10+
### Fixed
11+
12+
- Fix incorrect double invocation of menu items, listbox options and combobox options ([#3766](https://github.com/tailwindlabs/headlessui/pull/3766))
1113

1214
## [2.2.6] - 2025-07-24
1315

packages/@headlessui-react/src/hooks/use-quick-release.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export function useQuickRelease(
5757
) {
5858
// Capture the timestamp of when the `pointerdown` event happened on the
5959
// trigger.
60-
let triggeredAtRef = useRef<Date | null>(null)
60+
let triggeredAtRef = useRef<number | null>(null)
6161
let startXRef = useRef<number | null>(null)
6262
let startYRef = useRef<number | null>(null)
6363
useDocumentEvent(enabled && trigger !== null, 'pointerdown', (e) => {
@@ -67,14 +67,17 @@ export function useQuickRelease(
6767
startXRef.current = e.x
6868
startYRef.current = e.y
6969

70-
triggeredAtRef.current = new Date()
70+
triggeredAtRef.current = e.timeStamp
7171
})
7272

7373
useDocumentEvent(
7474
enabled && trigger !== null,
7575
'pointerup',
7676
(e) => {
77-
if (triggeredAtRef.current === null) return
77+
let triggeredAt = triggeredAtRef.current
78+
if (triggeredAt === null) return
79+
triggeredAtRef.current = null
80+
7881
if (!DOM.isHTMLorSVGElement(e.target)) return
7982

8083
// Ensure we moved the pointer a bit before considering it a quick
@@ -88,15 +91,12 @@ export function useQuickRelease(
8891

8992
let result = action(e as PointerEventWithTarget)
9093

91-
let diffInMs = new Date().getTime() - triggeredAtRef.current.getTime()
92-
triggeredAtRef.current = null
93-
9494
switch (result.kind) {
9595
case ActionKind.Ignore:
9696
return
9797

9898
case ActionKind.Select: {
99-
if (diffInMs > POINTER_HOLD_THRESHOLD) {
99+
if (e.timeStamp - triggeredAt > POINTER_HOLD_THRESHOLD) {
100100
select(result.target)
101101
close()
102102
}

0 commit comments

Comments
 (0)