inertiajs / inertia

Inertia.js lets you quickly build modern single-page React, Vue and Svelte apps using classic server-side routing and controllers.
https://inertiajs.com
MIT License
6.53k stars 433 forks source link

Click event on Link component does not fire when I expect it to #1939

Open martinbean opened 3 months ago

martinbean commented 3 months ago

Versions:

Describe the problem:

The click event on a <Link> component does not fire when I expect it to. It seems to fire after before and start events. I’d expect the click event to be fired before these, as me clicking the link happens before any HTTP request is initiated (without Inertia being clairvoyant).

I was hoping to be able to use the click event to then stop propagation, i.e. for links where I want to open a modal instead of make a page visit. I was hoping to use the click event because I’m already defining a before callback in my custom component that wraps Inertia’s <Link> component:

<!-- ActionButton.vue -->
<script setup>
const props = defineProps({
  confirmationText: {
    required: false,
    type: String,
  },
  href: {
    required: true,
    type: String,
  },
});

const confirm = () => props.confirmationText ? window.confirm(props.confirmationText) : true;
</script>

<template>
  <Link v-bind:href="href" v-on:before="confirm">
    <slot></slot>
  </Link>
</template>

I was hoping, from a parent component, I could add a click handler that prevents the event:

<ActionButton v-on:click.prevent="openModal">Open modal</ActionModal>

But this is not possible given how late the click event is called by Inertia. It’s also fired after the start event, and the Inertia docs says the start event is not cancellable. So it’s just far too late in the lifecycle any way.

Steps to reproduce:

I’ve observed this behaviour by adding a <Link> component to a test app and adding event listeners to all events:

<InertiaLink
    href="#"
    v-on:before="console.log('Before')"
    v-on:start="console.log('Start')"
    v-on:progress="console.log('Progress')"
    v-on:success="console.log('Success')"
    v-on:error="console.log('Error')"
    v-on:cancel="console.log('Cancel')"
    v-on:finish="console.log('Finish')"
    v-on:click="console.log('Click')"
>
    Test link
</InertiaLink>

The following is logged in the console:

Before
Start
Click
Success
Finish
RobertBoes commented 3 months ago

i.e. for links where I want to open a modal instead of make a page visit

In that case, why would you use an Inertia link? Inertia links are intended for Inertia navigations, but you're not performing a navigation. This would also cause different behaviours, since Inertia's link would mimic following the href, updating the browser URL etc.

Not sure if prevent would be the right thing here, as the default is already prevented (an anchor that would perform a page visit). You could try @click.capture, which would use capturing, that travels from parent to child.

martinbean commented 3 months ago

In that case, why would you use an Inertia link?

@RobertBoes Because that’s what my custom ActionButton component wraps. I wanted to create a generic “button” component that I can use for links or actions like this, without having to drop conditional logic all over my pages to switch between <Link> components or plain HTML <button> elements.

This would work on a vanilla <a> tag:

<a href="#" v-on:click.prevent="openModal">Open modal</a>

No network request would be initiated. But the same behaviour is not observed on Inertia’s <Link> component.

As mentioned, I’d expect a false-y click callback to prevent any network requests, since the network request happens in response to my click.

RobertBoes commented 3 months ago

Inertia just uses a regular Vue onClick event;

https://github.com/inertiajs/inertia/blob/da297accbb78f9d14f766dbfdb04c517c05a7c5c/packages/vue3/src/link.ts#L93

So the same behaviour shouldn't work in Vue either way, right? Being an anchor that's wrapped by a component, which in turn is also wrapped again. Guess it could only work if the event would also be emitted. What you're showcasing with a simple anchor tag isn't the same as what you're doing, as the click.prevent would directly call event.preventDefault() on the anchor click event.

I see Inertia is doing a check to see if preventDefault is called; https://github.com/inertiajs/inertia/blob/da297accbb78f9d14f766dbfdb04c517c05a7c5c/packages/core/src/shouldIntercept.ts#L5