Toast: `onClick` is bound twice, so the handler runs twice per click
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 6.9k
- Forks
- 1.1k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 57
Description
Environment
Nuxt 4.4.2, Vue 3.5.27, node 22, happy-dom. Verified on @nuxt/ui 4.4.0; the two lines below are unchanged on main.
Is this bug related to Nuxt or Vue?
Vue
Package
v4.x
Version
v4.4.0 (source unchanged through v4.11.1)
Reproduction
const toast = useToast()
toast.add({ title: 'Click me', onClick: (arg) => console.log('clicked', arg) })
Click the toast. The handler runs twice, logging clicked MouseEvent and then clicked Toast. Verified in a component test that mounts UApp, dispatches one click on the rendered li and counts invocations: 2, with argument types ['MouseEvent', 'Toast'].
Description
Toaster.vue puts onClick on <UToast> twice:
https://github.com/nuxt/ui/blob/main/src/runtime/components/Toaster.vue#L133
v-bind="omit(toast, ['id', 'close', '_duplicate', '_updated'])" <!-- onClick is still in here -->
...
@click="toast.onClick && toast.onClick(toast)" <!-- line 149 -->
ToastProps declares no onClick prop and ToastEmits no click emit, so the spread onClick falls through as a native listener. Vue's mergeProps keeps both handlers (it drops a duplicate only when the function reference is identical, and the inline @click is a different function), so a single click invokes the user handler twice, and with two different arguments. That also contradicts the documented signature onClick?: (toast: Toast) => void in useToast.ts: one of the two calls hands the handler a MouseEvent.
The practical damage is that any non-idempotent handler misbehaves. A handler that adds a toast stacks two of them.
Additional context
Same bug and same fix as #5784 in UEditorToolbar, resolved in cbed0cc by adding 'onClick' to the omit list. The equivalent here:
-v-bind="omit(toast, ['id', 'close', '_duplicate', '_updated'])"
+v-bind="omit(toast, ['id', 'close', '_duplicate', '_updated', 'onClick'])"
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/runtime/components/Toaster.vue, especially the UToast binding around lines 133 and 149, and compare the documented callback signature in useToast.ts. Run or extend the component test that mounts UApp and dispatches one click on the rendered li; done means the handler runs once with a Toast argument.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 90/100