earmsby's avatar

Modal and datalist problem

I have a livewire component that has a select form element and a button that when clicked brings up a modal form to add a new item to the select. I recently tried to replace the select element with an input element & datalist (for improved user experience selecting from a long list). I didn't have much problem adjusting the method that runs on wire:change and that was all working fine. However, when using the input/datalist vs. the plain select, the modal is not hidden when it is supposed to be.

To show/hide the modal, I do this in the livewire blade:

@if($showAddContractModal) @include('modals.contracts_quick_add') @endif

I've logged the value of $showAddContractModal even inside the blade and the value shows false but the modal is still visible.

If I simply comment out the datalist in the blade file, the modal works as expected.

I'm relatively new at working with datalists so maybe there's something intrinsic with them that is screwing up the functioning of the modal.

Any ideas? Thanks!

0 likes
8 replies
LaryAI's avatar

It sounds like the issue is related to how Livewire handles DOM updates, especially when using <input list="..."> with a <datalist>. When Livewire re-renders the component, it may not properly remove the modal from the DOM if the datalist or input is interfering with the DOM diffing process.

Here are some steps and solutions to try:

1. Use wire:key for Unique Elements

Livewire sometimes struggles with dynamic DOM elements. Assigning a unique wire:key to your modal and input can help Livewire track them correctly.

Example:

<input
    list="contracts"
    wire:model="selectedContract"
    wire:key="contract-input"
/>
<datalist id="contracts" wire:key="contracts-datalist">
    @foreach($contracts as $contract)
        <option value="{{ $contract->name }}">
    @endforeach
</datalist>

@if($showAddContractModal)
    @include('modals.contracts_quick_add', ['key' => 'add-contract-modal'])
@endif

And in your modal partial:

<div wire:key="{{ $key }}">
    <!-- Modal content -->
</div>

2. Ensure Modal is Not Persisted by JavaScript

If you are using JavaScript to show/hide the modal (e.g., Bootstrap, Alpine.js), make sure you are not manually showing the modal after Livewire has removed it from the DOM. Livewire removes the modal blade code, but if JS has already shown the modal, it might persist.

Solution:
Listen for Livewire events and close the modal via JS when $showAddContractModal becomes false.

Example:

<!-- In your blade -->
@if($showAddContractModal)
    @include('modals.contracts_quick_add')
@endif

@push('scripts')
<script>
    Livewire.on('closeContractModal', () => {
        // If using Bootstrap modal
        $('#contractsQuickAddModal').modal('hide');
    });
</script>
@endpush

And in your Livewire component:

public function closeModal()
{
    $this->showAddContractModal = false;
    $this->emit('closeContractModal');
}

3. Double-Check for DOM Conflicts

Sometimes, having a <datalist> with the same id as another element or having duplicate IDs can cause issues. Ensure all IDs are unique.

4. Try Removing the Datalist Temporarily

You mentioned that commenting out the datalist fixes the issue. Try simplifying your input/datalist to the bare minimum to see if a specific attribute or value is causing the problem.

5. Use Alpine.js for Modal Control (Optional)

If you want more control, you can use Alpine.js to handle modal visibility, syncing it with Livewire via events.


Summary:

  • Use wire:key on dynamic elements.
  • Ensure JavaScript isn't keeping the modal open after Livewire removes it.
  • Emit events from Livewire to close modals via JS.
  • Double-check for duplicate IDs.
  • Simplify your datalist to debug.

If you share your modal code and how you trigger it, I can provide a more tailored solution!

earmsby's avatar

I'm still struggling with this so I made a simpler test version of this in a sandbox project.

Here's a summary of what I need to do:

Show an input linked to a datalist populated from a Model (in this case Accounts) Below the input, show a button to add a new account The button triggers a modal to add the account. When you click the "add" button on the modal form, the account should be added to the table, the datalist should be updated to include the newly added account and then the modal should be hidden again.

Here is my modal form's code:

<div class="bg-white shadow-md max-w-sm mx-auto mt-12 mb-12 rounded-md fixed inset-0">
    <div class="flex flex-col h-full justify-between">
        <header class="p-6">
            <h3 class="font-bold text-lg">
                Modal Form
            </h3>
        </header>

        <main class="px-6 mb-4 overflow-auto">
            <form>
                <x-forms.input label="Contract Number" name="contract_number"/>
                <x-forms.input label="Some Other Field" name="other_field"/>
            </form>
        </main>

        <footer class="flex justify-end px-6 py-4 mt-6 bg-gray-200 rounded-b-md">
            <x-forms.button type="button" wire:click="hideModalPopUp()">Cancel</x-forms.button>
            <x-forms.button type="button" wire:click="createNewThing()">Add The Thing</x-forms.button>
        </footer>
    </div>
</div>

Here is the livewire blade:

<x-forms.button
    type="button"
    class="mt-4"
    wire:click="showModalPopUp()"
>
    {{ $buttonText }}
</x-forms.button>

@if($showModal)
    @include('modal_form')
@endif

And here is the livewire component code: class TestDataListModal extends Component { public $showModal = false; public $accounts=[]; public $buttonText='Modal Pop-up';

public function mount()
{
    $this->accounts=Account::all()->sortBy('displayName');
}

public function showModalPopUp()
{
    $this->showModal = true;
}

public function hideModalPopUp()
{
    $this->showModal = false;
}

public function createNewThing(){

    //add a new account
    $account=Account::create([
        'account_number' => '9999-EA',
        'account_type_id' => 1,
        'first_name'=>'Ellie',
        'last_name'=>'Armsby',
        'displayName'=>'Armsby, Ellie',
    ]);
    //refresh the list of accounts
    $this->accounts=Account::all()->sortBy('displayName');

    $this->showModal = false;
}
public function render()
{
    return view('livewire.test-data-list-modal');
}

}

I pinpointed that if I comment out the line in the component class: $this->accounts=Account::all()->sortBy('displayName'); Then everything works, except (of course) refreshing the datalist. But the account is added and the modal gets hidden.

If I leave that line in, the method runs fully (the account is added and the showModal variable is set to false, but the modal remains visible.

I suspect the problem is that the livewire blade isn't run again, so the conditional that shows/hides the modal isn't triggered. Is there a way to force that?

earmsby's avatar

For some reason, parts of my code above are being stripped out, making it hard to see what I'm doing. Is there a trick to marking up code so this doesn't happen?

Please or to participate in this conversation.