nuxt / nuxt/ui

Toast: `onClick` is bound twice, so the handler runs twice per click

Open Beginner friendly
#6,972 0 comments 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.