rob-balfre / svelte-select

Svelte Select. A select component for Svelte
https://svelte-select-examples.vercel.app
Other
1.25k stars 175 forks source link

Create item example code causes infinite loop #537

Closed traed closed 1 year ago

traed commented 1 year ago

This example seems to have some issues: https://svelte-select-examples.vercel.app/examples/advanced/create-item

If filterText is an exact match for an item already in the list it will cause an infinite loop where the on:filter callback is called continuously and the main thread freezes. I solved this by first checking if the item is already in the list before updating the array:

function handleFilter(e) {
  if (e.detail.length === 0 && filterText.length > 0) {
    const exists = items.some((i) => i.value.toLowerCase() === filterText.toLowerCase());

    if (!exists) {
      const prev = items.filter((i) => !i.created);
      items = [...prev, { label: filterText, value: filterText, created: true }];
    }
  }
}

... but I'm guessing the root cause is some internal bug.

The example also uses id instead of value for the placeholder item which is a bit confusing.

rob-balfre commented 1 year ago

@traed thanks for the feedback. I'm struggling to reproduce the infinite loop. Did you make any other changes to the example?

You're right about id, will update.

traed commented 1 year ago

Did some more testing on a fresh install. I'm able to reproduce it by setting the multiple property to true and then starting with an empty list of items, adding an item, then searching for that exact item.

rob-balfre commented 1 year ago

@traed nice, thanks.

I've tweaked the code for multiple... here you go. I'll add this to the examples site soon too.

<script>
    import Select from 'svelte-select';

    let filterText = '';

    let value = null;

    let items = [
        { value: 1, label: 'name 1' },
        { value: 2, label: 'name 2' },
        { value: 3, label: 'name 3' },
        { value: 4, label: 'name 4' },
        { value: 5, label: 'name 5' },
    ];

    function handleFilter(e) {        
        if (value?.find(i => i.label === filterText)) return;
        if (e.detail.length === 0 && filterText.length > 0) {
            const prev = items.filter((i) => !i.created);
            items = [...prev, { value: filterText, label: filterText, created: true }];
        }
    }

    function handleChange(e) {
        items = items.map((i) => {
            delete i.created;
            return i;
        });
    }
</script>

<Select on:change={handleChange} multiple on:filter={handleFilter} bind:filterText bind:value {items}>
    <div slot="item" let:item>
        {item.created ? 'Add new: ' : ''}
        {item.label}
    </div>
</Select>
CatalanCabbage commented 1 year ago

I ran into the same thing, did something similar today.
@rob-balfre if I may - it would be very nice to have some comments. I was pretty confused as to what value stands for, what e.detail is etc.

I ended up writing it like this today.


function handleFilter(e) {
    let isNewTagSelectedAlready = selectedTags?.find(tag => tag.value === filterText); 
    if (isNewTagSelectedAlready) {
        return;
    }

    let possibleResults = e.detail;
    let isNewTag = (possibleResults.length === 0 && filterText.length > 0);
    if (isNewTag) {
        const prev = items.filter((i) => !i.created);
        items = [...prev, { value: filterText, label: filterText, created: true }];
    }
}

Of course, it could just be since I'm not very proficient with JS yet :)

Liritt commented 9 months ago

Hi, I got the same error when using the code in the documentation, but traed code is working perfectly fine so I will use it instead