ui icon indicating copy to clipboard operation
ui copied to clipboard

Inertia: onSelect in dropdown items stop working

Open wrtisans opened this issue 9 months ago • 11 comments

Environment


  • Operating System: Darwin
  • Node Version: v22.14.0
  • Nuxt Version: -
  • CLI Version: 3.25.0
  • Nitro Version: -
  • Package Manager: [email protected]
  • Builder: -
  • User Config: -
  • Runtime Modules: -
  • Build Modules: -

Is this bug related to Nuxt or Vue?

Vue

Version

3.1.0

Reproduction

NA

Description

I’m using Vue 3 with Inertia. I upgraded to v3.1 and now it behaves strangely: components—like a dropdown menu inside a button group—redirect instead of honoring the onSelect and performing the intended actions. In my case, I want to update a ref when clicking a dropdown item and open a drawer containing a form.

Additional context

Laravel 12 with vue 3 and nuxtui 3.1

Logs


wrtisans avatar Apr 24 '25 22:04 wrtisans

i am experiencing the same issue, so far I haven't found a solution just yet. any button behaves like a link and subsequently refreshes the current page.

using e.preventDefault() or @click.prevent(() => {}) don't help either.

Same stack. Nuxt UI Pro 3.1, Laravel 11, and Inertia 2.0.7.

boulama avatar Apr 25 '25 01:04 boulama

Same here. Somehow all buttons now render as nested anchor tags with href="#".

fusigabs avatar Apr 25 '25 02:04 fusigabs

DId you guys set the inertia option? 🤔 https://ui.nuxt.com/getting-started/installation/vue#inertia

benjamincanac avatar Apr 25 '25 14:04 benjamincanac

Yes I did, yet all buttons now are rendered like this:

<a custom href="">
   <a href='#' class='...'>Hello</a>
</a>

this happens even when I pass the type='button' and as='button' props. These props seem to be ignored completely.

fusigabs avatar Apr 25 '25 14:04 fusigabs

yep. the same here. all the buttons render as anchor tag

wrtisans avatar Apr 25 '25 15:04 wrtisans

Yes I did, yet all buttons now are rendered like this:

<a custom href="">
   <a href='#' class='...'>Hello</a>
</a>

this happens even when I pass the type='button' and as='button' props. These props seem to be ignored completely.

As a result, the month change in the calendar component no longer works.

jvbianchi avatar Apr 25 '25 17:04 jvbianchi

​It appears that the url is always defined at this line, causing the component to render as an a tag by default

https://github.com/nuxt/ui/blob/9f7f5910ee3374a413bb8e5a889a9aa31fff9ae3/src/runtime/inertia/components/Link.vue#L113

Additionally, the InertiaLink component is consistently rendered, leading to full page reloads even when a simple button behavior is intended.

jvbianchi avatar Apr 25 '25 17:04 jvbianchi

​It appears that the url is always defined at this line, causing the component to render as an a tag by default

ui/src/runtime/inertia/components/Link.vue

Line 113 in 9f7f591

const url = computed(() => props.to ?? props.href ?? '#') Additionally, the InertiaLink component is consistently rendered, leading to full page reloads even when a simple button behavior is intended.

indeed, I cloned the repo locally and it does appear that for buttons, replacing # with null solves the problem for buttons.

however, it doesn't seem to be doing anything for dropdowns (i.e the one in the dashboard sidebar).

boulama avatar Apr 25 '25 18:04 boulama

https://github.com/nuxt/ui/blob/9f7f5910ee3374a413bb8e5a889a9aa31fff9ae3/src/runtime/inertia/components/Link.vue#L118-L132

I think that, the issue arises because the InertiaLink component is rendered even when the url is null. In such cases, it attempts to navigate, potentially leading to unexpected behavior like linking to the current page or triggering navigation events.

jvbianchi avatar Apr 25 '25 19:04 jvbianchi

I created a PR #3989 for this issue.

jvbianchi avatar Apr 26 '25 00:04 jvbianchi

Since the InertiaLink component is not implemented as a renderless component, the custom flag cannot be utilized. Similarly, LinkBase.vue should also be separated using unplugin. Consequently, the LinkBase.vue file will need to be updated. Below is a simple code draft I put together; please note that it has not yet been tested.

As for the Link.vue file, only specific sections have been modified.

  • inertia/components/Link.vue
  ...
  <template v-if="!isExternal">
    <slot
      v-if="custom"
      v-bind="{
        ...routerLinkProps,
        ...$attrs,
        as,
        type,
        disabled,
        href: url,
        active: isActive
      }"
    />
    <ULinkBase
      v-else
      v-bind="{
        ...routerLinkProps,
        ...$attrs,
        as,
        type,
        disabled,
        href: url,
        active: isActive
      }"
      :class="linkClass"
    >
      <slot :active="isActive" />
    </ULinkBase>
  </template>
  ...
  • inertia/components/LinkBase.vue
<script setup>
import { Link as InertiaLink } from '@inertiajs/vue3'
import { Primitive } from 'reka-ui'
import { computed, useAttrs } from 'vue'

const props = defineProps({
  as: { type: String, default: 'button' },
  type: { type: String, default: 'button' },
  disabled: Boolean,
  onClick: [Function, Array],
  href: String,
  navigate: Function,
  target: [String, Object, null],
  rel: [String, Object, null],
  active: Boolean,
  isExternal: Boolean,
})

const attrs = useAttrs()

function onClickWrapper(e) {
  if (props.disabled) {
    e.preventDefault()
    return
  }
  const handlers = Array.isArray(props.onClick) ? props.onClick : [props.onClick]
  handlers.forEach(fn => fn?.(e))
  if (props.href && props.navigate && !props.isExternal) {
    props.navigate(e)
  }
}

const componentProps = computed(() => {
  if (props.href) {
    const base = {
      'href': props.disabled ? undefined : props.href,
      'aria-disabled': props.disabled ? 'true' : void 0,
      'role': props.disabled ? 'link' : void 0,
      'tabindex': props.disabled ? -1 : void 0,
    }

    let isExternal = props.isExternal

    // optionally, Do you want automatically detect external links?
    if (props.href.startsWith('http') && typeof window !== 'undefined') {
      const url = new URL(props.href)
      isExternal = url.host === window.location.host
    }

    return isExternal || props.disabled
      ? { as: 'a', ...base }
      : { as: InertiaLink, ...base, ...attrs }
  }

  if (props.as === 'button') {
    return { as: 'button', type: props.type, disabled: props.disabled }
  }

  return { as: props.as }
})
</script>

<template>
  <Primitive
    v-bind="componentProps"
    :rel="rel"
    :target="target"
    @click="onClickWrapper"
  >
    <slot />
  </Primitive>
</template>

anonymouslab avatar Apr 26 '25 09:04 anonymouslab

@romhml @benjamincanac Could this issue be reopened?

Mallon94 avatar May 09 '25 14:05 Mallon94

@benjamincanac I tested out the latest pkg.pr.new/@nuxt/ui@1fa5e5b with this fix but the onSelect issue still persists for me. Did you get it working on your end with this fix?

Mallon94 avatar May 13 '25 19:05 Mallon94

@Mallon94 Really? Yes I merged my PR because everything was working fine 🤔 Did you set the @nuxt/ui resolution correctly?

{
  "resolutions": {
    "@nuxt/ui": "https://pkg.pr.new/@nuxt/ui@b9adc83"
  }
}

benjamincanac avatar May 14 '25 09:05 benjamincanac

@benjamincanac apologies, i used the wrong package. This is working. Thank you very much!

Mallon94 avatar May 14 '25 10:05 Mallon94