Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autocomplete input field not updating #766

Open
DonCamillo11 opened this issue Nov 28, 2024 · 31 comments
Open

Autocomplete input field not updating #766

DonCamillo11 opened this issue Nov 28, 2024 · 31 comments
Assignees

Comments

@DonCamillo11
Copy link

DonCamillo11 commented Nov 28, 2024

I have a strange behavior with the autocomplete. The input field does not update after a new value has been set. If I save the value and reload the page, the value is there. To test it, I put a normal input field on top and it works as it should. The value is set correctly, otherwise it would not be set in the input field. No idea why this is happening, it seems to be a simple input field.

Edited: I forgot to mention that the value does not come from the autocomplete list. It can be any term from the database. It is working with a value from the autocomplete list.

This is my code:

<flux:input wire:model="document.items.{{ $index }}.title" />
<flux:autocomplete wire:model="document.items.{{ $index }}.title" placeholder="{{ __('Item') }}" >
    @foreach($products as $product)
        <flux:autocomplete.item wire:click="addProduct('{{ $product->id }}', {{ $index }})">{{ $product->title }}</flux:autocomplete.item>
    @endforeach
</flux:autocomplete>

Autocomplete after updating the value
Image

Autocomplete after reload
Image

@jeffchown
Copy link

@DonCamillo11 Without an code example that can be copy/pasted to reproduce the issue, best I can do is suggest:

  • first, test it without the wire:click on the flux:autocomplete.item as that could be conflicting with the .item's default behaviour
  • if that works, extract the addProduct to a separate button to avoid the conflict

@DonCamillo11
Copy link
Author

@jeffchown Thanks, but unfortunately this doesn’t solve the issue. Problem is, that this is a quite complex structure with nested components. Not that easy to extract a code example. I think it has to do with dynamically generates elements. I will see if I can extract a code example.

@DonCamillo11
Copy link
Author

For now I figured out that removing the autocomplete attribute from the <ui-select> tag solves the issue. Any ideas why this is happening? It seems to work as expected without the autocomplete attribute

@jeffchown
Copy link

@DonCamillo11 Without a complete code sample to reference/use, best next guess is you could try adding :filter="false" to your autocomplete

@DonCamillo11
Copy link
Author

DonCamillo11 commented Nov 28, 2024

@jeffchown OK, here is a simple example:

namespace App\Livewire\Test;

use Livewire\Component;

class Test extends Component
{
    public $testfield;
    public $document;

    public function mount() 
    {
        $this->document['items'][] = [
            'order' => 1,
            'total' => 2,
            'title' => 'Title',
        ];
    }

    public function test() 
    {
        
        $this->document['items'][] = [
            'order' => count($this->document['items'])+1,
            'total' => 0,
            'title' => 'test',
        ];
        
    }

    public function render()
    {
        return <<<'HTML'
        <div>
            @foreach($document['items'] as $index => $item)
            <flux:input wire:model="document.items.{{ $index }}.title" />
            <flux:autocomplete name="document[items][{{ $index }}][title]" wire:model="document.items.{{ $index }}.title" placeholder="{{ __('Item') }}">

                    <flux:autocomplete.item>Test 1</flux:autocomplete.item>
                    <flux:autocomplete.item>Test 1</flux:autocomplete.item>

            </flux:autocomplete>
            <button type="button" wire:click="test()">Click me</button>
            @endforeach
        </div>
        HTML;
    }
}

For the last element, the input field is not updated.

Removing the autocomplete from the autocomplete/index.blade.php solves the issue:

<ui-select autocomplete clear="esc" data-flux-autocomplete {{ $attributes->only('filter')->merge(['filter' => true]) }}>
    <flux:input :attributes="$attributes->except('filter')" />

    <flux:autocomplete.items>
        {{ $slot }}
    </flux:autocomplete.items>
</ui-select>

<ui-select clear="esc" data-flux-autocomplete {{ $attributes->only('filter')->merge(['filter' => true]) }}>
    <flux:input :attributes="$attributes->except('filter')" />

    <flux:autocomplete.items>
        {{ $slot }}
    </flux:autocomplete.items>
</ui-select>

@jeffchown
Copy link

@DonCamillo11 You example isn't quite complete, you have a line in your code x-on:focus="open{{$index}} = true" with no corresponding public function open() in the component.

@DonCamillo11
Copy link
Author

@jeffchown Just ignore, I forgot to delete that. I edited the example.

@jeffchown
Copy link

jeffchown commented Nov 28, 2024

@DonCamillo11 Having the same wire:model on multiple controls in the same Livewire component can cause issues

@DonCamillo11
Copy link
Author

DonCamillo11 commented Nov 28, 2024

@jeffchown Do you mean the input field and the autocomplete? This is just to show that it is working with the <flux:input>. Simply delete it.

@jeffchown
Copy link

@DonCamillo11 Also, your x-button is submitting the page, try <x-button wire:click.prevent="test()">Click me</x-button>

@jeffchown
Copy link

@DonCamillo11 What exactly are you trying to accomplish? It's not clear from your description. Autocomplete may not be your best choice.

@DonCamillo11
Copy link
Author

DonCamillo11 commented Nov 28, 2024

@jeffchown Thanks, but this doesn’t matter for the example (but edited the example code).
This is just a basic example. I need to use the autocomplete because I have a list of available products to choose from. But it must also be possible to fill the field with values that aren't present in the autocomplete list. That's what I’m trying to do. But I think that doesn't matter for the example.

@jeffchown
Copy link

@DonCamillo11 It matters because I don't understand what your ultimate desired behaviour is. If you just want it to update without needing a page refresh, then add .live to your autocomplete's wire:model, if you want it to update when you press the Click me button (without refreshing the page, then add .prevent to the button's wire:click.

If not either of those, then I need more detail re: desired result/behaviour.

@DonCamillo11
Copy link
Author

DonCamillo11 commented Nov 28, 2024

@jeffchown As shown in the example, I want to update the value by click. But, as you can see, the last field isn’t set.

My <x-button> is type="button" so it does not send the form in my case, but forgot to mention.

@DonCamillo11
Copy link
Author

To make it clearer, here is an example of how it should work. But at the end, you can see, the desired title field is not set.

Bildschirmaufnahme.2024-11-28.um.16.23.28.mov

@jeffchown
Copy link

@DonCamillo11 I've edited your Blade to provide some more feedback:

<div>
    @foreach($document['items'] as $index => $item)
        {{-- <flux:input wire:model="document.items.{{ $index }}.title" /> --}}
        {{ $document['items'][$index]['title'] }}

        <flux:autocomplete
            name="document[items][{{ $index }}][title]"
            wire:model="document.items.{{ $index }}.title"
            placeholder="{{ __('Item') }}"
        >
            <flux:autocomplete.item>Test 1</flux:autocomplete.item>
            <flux:autocomplete.item>Test 2</flux:autocomplete.item>
        </flux:autocomplete>

        <x-button wire:click="test()">Click me</x-button>

        <div>
            Document:
            <pre>
                @php
                    echo var_dump($document);
                @endphp
            </pre>
        </div>
    @endforeach
</div>

On initial display:

Image

After entering Title2 in the autocomplete and clicking Click me:

Image

It is doing exactly what your code is telling it to, updating the current value then adding another item to the bottom of the $document array via:

$this->document['items'][] = [
            'order' => count($this->document['items'])+1,
            'total' => 0,
            'title' => 'test',
        ];

@DonCamillo11
Copy link
Author

DonCamillo11 commented Nov 28, 2024

It’s not doing exactly what the code is telling it to. The expected behaviour is a new item in the $document array that shows up at the bottom. But it should show the title 'test' as defined in the array. But what I get is an empty field. The title is set on the second click.

Bildschirmaufnahme.2024-11-28.um.16.52.48.mov

Testing it with a simple input field on top of the autocomplete, it shows the expected behaviour.

Bildschirmaufnahme.2024-11-28.um.17.05.53.mov

@jeffchown
Copy link

jeffchown commented Nov 28, 2024

@DonCamillo11 That helps!

The flux:autocomplete is actually a ui-select under-the-hood. Therefore, if you type a value in it's input field that is not one of the valid flux:autocomplete.items (e.g. options), it cannot autopopulate it's input on next (re)render because it cannot find a matching (e.g. valid) item/option.

One solution would be to add the 'custom' value entered into the autocomplete's input to each subsequent autocomplete's flux:autocomplete.item list on (re)render.

@DonCamillo11
Copy link
Author

@jeffchown Thanks for the solution, but thats not working either. Tried to add <flux:autocomplete.item>{{ $document['items'][$index]['title'] }}</flux:autocomplete.item> to the <flux:autocomplete> but the field is always empty on re-render. Unfortunately, that is not the solution or the behavior I would expect from the autocomplete.

As mentioned above, for now I use the ui-select without the autocomplete attribute.

@jeffchown
Copy link

@DonCamillo11 This is what I was trying to describe:

in your component:

<?php

namespace App\Livewire;

use Livewire\Attributes\Computed;
use Livewire\Component;

class Test766 extends Component
{
    public $testfield;

    public array $document = [];

    public function mount()
    {
        $this->document['items'] = [];

        $this->document['items'][] = [
            'key' => str()->random(5),
            'order' => 1,
            'total' => 2,
            'title' => 'Test',
        ];
    }

    #[Computed()]
    public function options()
    {
        // default options
        $options = [
            'Test',
            'Test 1',
            'Test 2',
        ];

        // check for existence of $document['items']['index']['title'] in $options
        // if not already there, add it
        return collect($options)
            ->merge(collect($this->document['items'])->pluck('title'))
            ->unique()
            ->toArray();
    }

    public function test()
    {
        // add next item
        $this->document['items'][] = [
            'key' => str()->random(5),
            'order' => count($this->document['items']) + 1,
            'total' => 0,
            'title' => 'Test',
        ];
    }

    public function render()
    {
        return view('livewire.test766');
    }
}

your Blade:

<div>
    @foreach($document['items'] as $index => $item)
        {{ $document['items'][$index]['title'] }}

        <flux:autocomplete
            :wire:key="$document['items'][$index]['key']"
            name="document[items][{{ $index }}][title]"
            wire:model="document.items.{{ $index }}.title"
            placeholder="{{ __('Item') }}"
        >
            @foreach ($this->options as $option )
                <flux:autocomplete.item>{{ $option }}</flux:autocomplete.item>
            @endforeach
        </flux:autocomplete>

        <x-button wire:click="test()">Click me</x-button>

        <br><br>
    @endforeach
</div>

produces this:

Image

@DonCamillo11
Copy link
Author

DonCamillo11 commented Nov 28, 2024

OK, I think we're stuck :) This isn't working as expected and in your screenshot the last field is empty and only shows the placeholder, not the title that is defined here

$this->document['items'][] = [
'order' => count($this->document['items']) + 1,
'total' => 0,
'title' => 'Test',
];

Image

@jeffchown
Copy link

jeffchown commented Nov 28, 2024

@DonCamillo11 Given your example/video, my suggestion addresses the issue re: custom values. The last one could be fixed with :selected="$document['items'][$index]['title'] == $option" on the autocomplete item.

That said, based on your initial "It can be any term from the database", I don't believe this implementation is the most effective.
If you mean you want the autocomplete to search a database table based on what's entered (as the user types)and let them confirm a selection then there is another way to accomplish this using :filter="false".

I'm taking a break, then will see if I can put an example together.

@jeffchown
Copy link

jeffchown commented Nov 28, 2024

@DonCamillo11 UPDATE: Whew! We got there in a roundabout way 🙃, but now that I (think) I totally understand your issue, and after trying a few different things to make sure we weren't missing anything, I've been able to reproduce it consistently using the code below.

@calebporzio @joshhanley Please help @DonCamillo11 and I figure this one out, we've spent a lot of time on it. Thanks in advance.

Summary:

  • renders a multidimensional form using an array to represent each row in the form ($orders)
  • the mount() method adds a starting $order[] with default title of Title 1
  • on initial render the flux:autocomplete correctly shows the Title 1 value
  • click the Save + Add Row button, the save() method adds a new row to $orders[] array, again with the default title of Title 1
  • the page (re)renders and shows the controls for the new row but the flux:autocomplete does not correctly show the default value of Title 1, it is instead set to blank and shows the placeholder. UPDATE: but if you click on the empty input, the dropdown does correctly show the default value of Title 1, so it looks like it is only the autocomplete's input's initial value that is not being initialized properly when dynamically added
  • click the Save + Add Row button again
  • the 2nd row's flux:autocomplete now correctly shows the Title 1 value
  • the new 3rd row's flux:autocomplete does not show the correct Title 1 default
  • etc...

Code:

\App\Models\Document

<?php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Sushi\Sushi;

final class Document extends Model
{
    use Sushi;

    protected $rows = [
        ['id' => 1, 'title' => 'Title 1'],
        ['id' => 2, 'title' => 'Title 2'],
        ['id' => 3, 'title' => 'Title 3'],
        ['id' => 4, 'title' => 'Title 4'],
        ['id' => 5, 'title' => 'Title 5'],
        ['id' => 6, 'title' => 'Title 6'],
    ];
}

resources\views\livewire\766.blade.php // for this issue #766, tested in view using <livewire:766 />

<?php

use Livewire\Volt\Component;

use App\Models\Document;

new class extends Component {

    public array $orders = [];

    public function mount()
    {
        // seed one starting order
        $this->orders[] = [
            'id' => 1,
            'total' => 1,
            'title' => 'Title 1',
        ];
    }

    public function documentsMatching(string $title)
    {
        if (empty($title)) {
            return [];
        }

        return Document::query()
            ->whereLike('title', '%'.$title.'%')
            ->orderBy('title')
            ->pluck('title', 'id');
    }

    public function save()
    {
        // add next order w/ default as 'Title 1'
        $this->orders[] = [
            'id' => count($this->orders) + 1,
            'total' => count($this->orders),
            'title' => 'Title 1',
        ];
    }
};
?>

<div>
    @forelse($orders as $index => $order)
        <div class="flex items-center mt-1 space-x-4">
            <div class="w-[200px]">{{ $orders[$index]['title'] }}</div>

            <div class="w-[400px] flex flex-col">
                <flux:autocomplete
                    wire:model.live="orders.{{ $index }}.title"
                    :filter="false"
                    :wire:key="$orders[$index]['id']"
                    placeholder="Choose Document Title..."
                >
                    @foreach ($this->documentsMatching($orders[$index]['title']) as $id => $title )
                        <flux:autocomplete.item wire:key="{{ $orders[$index]['id'] }}-{{ $id }}">
                            {{ $title }}
                        </flux:autocomplete.item>
                    @endforeach
                </flux:autocomplete>
            </div>

            <div>
                <flux:button wire:click="save">Save + Add Row</flux:button>
            </div>
        </div>
    @empty
        No Orders Yet
    @endforelse
</div>

@DonCamillo11
Copy link
Author

DonCamillo11 commented Nov 28, 2024

@jeffchown Thank you for your efforts. I think we are getting closer to the problem now 😀 Let’s see if there is a solution ...

@jeffchown
Copy link

You're welcome, @DonCamillo11 🤞

@calebporzio
Copy link
Contributor

Wow, this one looks deep. Thank you for distilling it down for us @jeffchown & @DonCamillo11, we will look into this soon when we're back from break

@jeffchown
Copy link

You're welcome, @calebporzio
Thanks.

Happy (belated) Thanksgiving!

@joshhanley joshhanley self-assigned this Dec 19, 2024
@joshhanley
Copy link
Member

@DonCamillo11 thanks for reporting and thanks @jeffchown for helping narrow it down! I've submitted a PR with a fix.

@DonCamillo11
Copy link
Author

@joshhanley Thanks, but it’s still not working in v1.1.2.

@joshhanley
Copy link
Member

@DonCamillo11 PR hasn’t been merged yet, that’s why this issue is still open 🙂

@kylewallace
Copy link

@joshhanley do you have any updates on this PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants