huntabyte / bits-ui

The headless components for Svelte.
https://bits-ui.com
MIT License
1.19k stars 86 forks source link

Better <Select> DX - bind:value #655

Open justingolden21 opened 3 weeks ago

justingolden21 commented 3 weeks ago

Describe the feature in detail (code, mocks, or screenshots encouraged)

To use the select in svelte, it takes a tremendous effort.

It took me and my friend over an HOUR to get a simple select working.

With a normal <select>, this is trivial and someone who started programing yesterday could get this:

<script>
    let selection = 'apple';
    let fruits = ['apple', 'banana'];
</script>

<select bind:value={selection}>
    {#each fruits as fruit}
        <option value={fruit}>{fruit}</option>
    {/each}
</select>

{selection}

But with bits, this takes a LOT of work:

<script>

    import {
        Select,
        SelectTrigger,
        SelectContent,
        SelectValue,
        SelectItem
    } from '$lib/components/ui/select';

    let selectedValue = $state('Option 1');

    const options = ['Option 1', 'Option 2', 'Option 3'];
</script>

<div>
    <h2>Select an option:</h2>
    <Select selected={{ value: selectedValue, label: selectedValue }} items={options.map(o=> ({ value: o, label: o }))}
        onSelectedChange={value => selectedValue = value.value}
        >
        <SelectTrigger>
            <SelectValue placeholder="Select an option" />
        </SelectTrigger>
        <SelectContent>
            {#each options as option}
            <SelectItem value={option}>{option}</SelectItem>
            {/each}
        </SelectContent>
    </Select>

    <p>Selected Value: {selectedValue}</p>
</div>

Specifically, this bit:

<Select 
        selected={{ value: selectedValue, label: selectedValue }} 
        items={options.map(o => ({ value: o, label: o }))}
        onSelectedChange={value => selectedValue = value.value}

This is absurdly complex, whereas in every other component, I could simple do bind:value={selectedValue}. These three lines took us an hour. I've been making websites for ten years and working with svelte for three years and my friend who helped me solve this is in a similar boat. We were pulling our hair out. We tried looking at the source code and there's no hint of this. We looked at the docs (https://www.bits-ui.com/docs/components/select) and it just shows items={themes} but no way to bind... The API reference has selected and items and onSelectedChange and everything else, but it has probably 50 different similar things too.

I can't imagine people making a select and not caring about the value within it, so rather than just saying binding is common, I'd go as far as to say that not binding is uncommon. Only in a form would you just have the submit event take care of the form value.

If there's ANY way to improve the DX of this, I'm sure the entire community would greatly appreciate it. I know it's a thankless job working on something like bits, and I've come here (as I'm sure many have) after the recent increase in shadcn users, and I know that creating all the enormous APIs for all these things is rough and <Select> has to support a LOT of use cases. I'm sure I'll be told to just use a vanilla HTML <select>, but I'd really like to use shadcn components and every other component other than this Select has been amazing to work with. It just amazes me that multiple experienced devs could waste an hour on this, and every other component is so simple and well designed and intuitive.

The problems are threefold:

  1. This code was very difficult to come up with. That's a combination of a few problems: documentation and complexity
  2. The documentation was not helpful to bind a value to select.
  3. Any time I want to use a select in my UI, it'll take about 12 lines of code rather than the 5 much cleaner and easier to read lines that one would expect from any other select component.

Thanks again for listening to my rant here, and I hope there's a solution to this. I have asked around and heard many others mentioning how long they struggled to get a select working. For a component this common and a use case (binding) this common, improving the DX like this would be a huge win for bits imo. Thanks again 😃

What type of pull request would this be?

Enhancement

Provide relevant links or additional information.

No response

justingolden21 commented 3 weeks ago

I realize I only need

selected={{ value: selectedValue, label: selectedValue }}
onSelectedChange={(value) => (selectedValue = value.value)}

but this is still a lot compared to bind:value={selectedValue}

and since it can be undefined, it's more like

selected={{ value: selectedValue , label: selectedValue }}
onSelectedChange={(value) => {
    if (value?.value) selectedValue = value?.value;
}}
jvolker commented 3 weeks ago

Sounds similar to this issue: https://github.com/huntabyte/bits-ui/issues/235

huntabyte commented 1 week ago

Yep, this is gonna be addressed in the Svelte 5 version. I've always hated using this specific variation of the Select myself in projects, so I understand your pain! Here's a preview of the new Select for reference https://huntabyte-next.bits-ui.pages.dev/docs/components/select 😃

justingolden21 commented 6 days ago

I'm glad to hear, thanks! Appreciate your hard work!

That new one works great!