wire-elements / modal

Livewire component that provides you with a modal that supports multiple child modals while maintaining state.
MIT License
1.09k stars 129 forks source link

Anomally on modal close & reopen quickly #408

Open razvan-p opened 5 months ago

razvan-p commented 5 months ago

This seems to be a bug on modal pro component. Steps to reproduce:

  1. Click to open the modal:

    <button
    type="button"
    onclick="Livewire.dispatch('modal.open', { component: 'create-model' })"
    >
    Open modal
    </button>
  2. Press the submit button. My code looks like:

    <x-wire-elements-pro::bootstrap.modal on-submit="save">
    ...
    <x-slot name="buttons">
        <div class="mt-2">
            <button type="submit">
                Save
            </button>
        </div>
    </x-slot>
    </x-wire-elements-pro::bootstrap.modal>
public function save()
{
    $this->authorize('save', $this->form->model);

    $model = $this->form->store();

    $this->close(
        andDispatch: [
            'modelSaved',
        ]
    );
}
  1. Quickly click to open the modal again.

The error in console:

Uncaught Snapshot missing on Livewire component with id: NWq3XntmBYjxHS3l8mVr
Screenshot 2024-01-10 at 12 39 03

PS: If the error doesn't appear on the first try, repeat again all the steps.

PhiloNL commented 5 months ago

Hi there, This error often occurs when Livewire is unable to morph content. Are you using a placeholder / lazy loading for example? You will need to ensure the root element is of the same time.

<x-wire-elements-pro::bootstrap.modal> will return a <form> element. If you have a placeholder that is a <div> it will trigger this error. Would you mind checking if this is the case?

razvan-p commented 5 months ago

I don't use any placeholder. My modal looks like:

<x-wire-elements-pro::bootstrap.modal on-submit="store" :content-padding="false">
    <x-slot name="title">
        {{ __('Title') }}
    </x-slot>

    <div class="col-md-12 scrollable-inside-fullscreen-modal">
        <div class="row g-3 mt-3">
            <div class="col-12 px-3">
                <strong>{{ __('Product') }}</strong>
            </div>
            <div class="col-md-12">
                {{ $form->product->name }}
            </div>
        </div>
        ...
    </div>

    <x-slot name="buttons">
        <div class="mt-2">
            @can('delete', $form->product)
            <button type="button" wire:loading.attr="disabled" wire:click="delete">
                <i class="fas fa-fw fa-trash-alt"></i>
            </button>
            @endcan
            <button type="submit" class="btn btn-tertiary btn-lg mx-2" wire:loading.attr="disabled">
                {{ __('Save') }}
            </button>
        </div>
    </x-slot>
</x-wire-elements-pro::bootstrap.modal>
jmitech commented 5 months ago

We also have this problem. The issue also arise when the modal is simply closed (button, click-away or ESC key) and quickly reopened. Thank you for your time. I bought the PRO license and this is a BIG issue for us.

Sometime, the JS error is not thrown, but the modal is not loading even if the overlay with unfocused effect is displayed. The page has to be reloaded.

Issue happens with and with our custom modal component

PhiloNL commented 5 months ago

Can you provide a simple example component that reproduces the problem? I've been unable to reproduce the issue following the steps mentioned: https://d.pr/v/Oid3ne

razvan-p commented 5 months ago

Please check this recording. If needed I can provide the code related to the submitted form which contains multiple select2 elements, foreach loops, if/else conditions and alpine js code.

https://github.com/wire-elements/modal/assets/105241697/46440e75-7bba-4d04-ba79-0c834a9b43e1

PhiloNL commented 5 months ago

Can you provide a repository with a fresh Laravel & Livewire install that reproduces the issue? There are so many factors at play due to third-party dependencies that it's very difficult to figure out the problem without having access to a working app that contains an example.

jmitech commented 5 months ago

Can you provide a simple example component that reproduces the problem? I've been unable to reproduce the issue following the steps mentioned: https://d.pr/v/Oid3ne

I'm suspecting some kind of timing issue with the DOM/Livewire at removal/creation of modal components...

I have identified two problems,

In the video, since your test page and test modal are very lite the issues aren't happening. I'm pretty sure you can still recreate problem 1 with this environment by using the ESC key to close the modal then quickly click on the trigger link (as fast as you can). At some point, you will get the blurred overlay showed without the actual modal, user has to refresh page. That's problem 1 for me.

I agree this is not a real-world action for a user to do, but, it can happen. Also, we had some workflows that made it happen programmatically too.

As for problem 2 (OP's exact issue i.e. Uncaught Snapshot missing on Livewire component with id: y8ItIRaMcNPXta5npwZE), it looks like it's triggered by the same mechanism (re-opening a modal too quickly) but we only could reproduce with Big livewire components that are [as I imagine] taking more time to be evaluated client-side. If I wait 1 or 2 seconds after the modal is closed, I can always re-open it without errors. But if I click too quickly, scripting stops and outputs the dreaded error. Note that the issue also happens when another record's modal is requested (does not have to be the same trigger btn/modal).

To recap:

  1. Problem 1: When user tries to re-open any modal too quickly. Modal doesn't pop. Blurred overlay is displayed. No error in console. User has to refresh.

  2. Problem 2: When user re-opens an heavy component's modal too quickly. Modal doesn't pop. Blurred overlay is shown (not always). Error in console and scripting breaks. User has to refresh page.

So, is problem 2 linked to problem 1? Are these big components of ours misconfigured or poorly written (hydration/datatypes/wire:keys/etc)?... maybe, probably! But is this the cause of problem 2 or not? That's what I'd like to know.

Hope it helps. I love wire-elements btw!

Thank you very much, Jim

PhiloNL commented 5 months ago

Thanks @jmitech So far I haven't been able to reproduce this issue so I have an assumption about what might be going on. When all modals are closed the state is destroyed (a request is made to the server to do this). A new modal is requested before the previous request has finished, which causes the state to be destroyed after the new modal has opened. The question is, why is the request to destroy the modal slower compared to opening a new modal? Because the latter should be slower.

Would you mind checking your devtools and watch the update events and see if this is the case? For me, the open request always seems to be slower in milliseconds compared closing.

Thanks!

27pchrisl commented 5 months ago

In my situation, this issue happens because a second modal is opened while resetState is in flight.

  1. setActiveComponent is called to set the component and show the modal
  2. setShowPropertyTo(false) is later called to close the modal
  3. setShowPropertyTo invokes $wire.resetState after 300ms, which starts an HTTP request with the current component state
  4. setActiveComponent is called to set the component and show a second modal before the request is complete
  5. $wire.resetState's HTTP request completes and updates the component, overwriting the change that was made in step 4, and sometimes causing corruption of the component state.

Because $wire.resetState returns a promise, it is likely possible to make subsequent setActiveComponent calls wait until it is complete first.

PhiloNL commented 3 months ago

I've added a click throttle in v4.0.14 that might also solve this problem. It does require you to use the wire:modal or wire:slide-over directives like: <button wire:modal="assets-show, @js(['releaseId' => $release->id)">Open</button>

avennera commented 1 month ago

Hi Philo! Adding wire:modal seems to works in that scenarios!. But which is the right way to use "wire:modal" in Blade context calls? Like: <button onclick="Livewire.dispatch('modal.open', {component: 'edit-user', arguments: {'user': 1}})">Open</button> ¿? Thanks!

PhiloNL commented 1 month ago

Hi Philo! Adding wire:modal seems to works in that scenarios!. But which is the right way to use "wire:modal" in Blade context calls? Like: <button onclick="Livewire.dispatch('modal.open', {component: 'edit-user', arguments: {'user': 1}})">Open</button> ¿? Thanks!

Hi Philo! Adding wire:modal seems to works in that scenarios!. But which is the right way to use "wire:modal" in Blade context calls? Like: <button onclick="Livewire.dispatch('modal.open', {component: 'edit-user', arguments: {'user': 1}})">Open</button> ¿? Thanks!

<button wire:modal="edit-user, @js(['user' => 1)">Open</button>

nenadsijan commented 5 days ago

Dear @PhiloNL this solution <button wire:modal="edit-user, @js(['user' => 1)">Open</button>works perfectly on the blade page, but how to use this wire:modalcall from Livewire controler ? Correctly for this scenario when i need to open new modal from backend. $this->dispatch('modal.open', component: 'modals.edit-user', arguments: ['uuid' => 'test-1212-123-test']); Thanks in advance Nenad

razvan-p commented 5 days ago

Dear @PhiloNL this solution <button wire:modal="edit-user, @js(['user' => 1)">Open</button>works perfectly on the blade page, but how to use this wire:modalcall from Livewire controler ? Correctly for this scenario when i need to open new modal from backend. $this->dispatch('modal.open', component: 'modals.edit-user', arguments: ['uuid' => 'test-1212-123-test']); Thanks in advance Nenad

Hi there! Also what if we open the modal from a non livewire page (where wire:modal directive is not available). For example like this:

<button onclick="Livewire.dispatch('modal.open', { component: 'show-warehouses-modal', arguments: { arg1: 1 } })">
    Manage Warehouses
</button>

More than 50% of my modals are managed like this because I don't need Livewire on every page except of this modal.