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

How to initialize when using justValue? #555

Closed josdejong closed 1 year ago

josdejong commented 1 year ago

It looks like Select resets any initial value when using justValue. When I try out the following in the Svelte REPL, the Select box is empty on load, instead of holding the initial value 'two':

https://svelte.dev/repl/08f8c864464a403d8033887ea06cbc26?version=3.55.1

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

    let collection = [
        { value: 'one', label: 'One' },
        { value: 'two', label: 'Two' },
        { value: 'three', label: 'Three' },
    ];

    // Issue: the Select box should be initialized with a selected value "two", but is loaded without any value selected
    let justValue = 'two';
</script>

<Select items={collection} bind:justValue />

{#if justValue}
<p>
    Selected value: {justValue}
</p>
{/if}
jorri11 commented 1 year ago

Im wondering the same. The documentation states that it is readonly, so there probably isnt an easy way around this. But i think it would make more sense for the value to work as justValue, but it being writable as well.

rob-balfre commented 1 year ago

It's read only. Use value instead please.

josdejong commented 1 year ago

Thanks for your reply Rob. And thanks for developing this excellent library, great work!

That justValue is readonly is a pity. I personally have never seen a case where I would need the selected option objects, I only need to bind the actual values themselves (available in justValue).

Right now, for every select box that I create I need to do plumbing to convert values to their matching options so I can feed that to <Select>, and the other way around to get the value from the option again. This feels like redundant and something that could be done by Select itself. All I need would be a two way bound justValue.

So the simple case of what I do now is the following. Is that indeed the intended way? Or are there simpler solutions to this?

https://svelte.dev/repl/d57ce13fa7ee49ed8d766d7a8d080715?version=3.55.1

<script>
  import Select from 'svelte-select'

  export let values = ['one', 'two']

  let options = [
    { value: 'one', label: 'One' },
    { value: 'two', label: 'Two' },
    { value: 'three', label: 'Three' },
  ]

  // 👇 ideally, all code below should not be needed (`justValue` could make this redundant)

  let selectedOptions = values
    .map(value => {
      return options.find(option => option.value === value)
    })
    .filter(option => !!option)

  $: values = selectedOptions.map(option => option.value)
</script>

<Select multiple items={options} bind:value={selectedOptions} />

{#if values}
  <p>
    Selected values: {values}
  </p>
{/if}
rob-balfre commented 1 year ago

Much simpler...

https://svelte.dev/repl/b0cbc60a7cb64b48a3449d832c15b953?version=3.55.1

josdejong commented 1 year ago

Ahh, thanks!

I have to say, it's weird and confusing that the value property of Select can hold two things: initially, you can let it contain the plain value (like justValue), and after initialization Select will turn value into the selected options {value, label}. It's mixing two different things in the same variable.

Would it be possible to make justValue bind two ways instead of being readonly? I'm glad there is a much simpler workaround, but it doesn't feel right to need this mixed type intermediate variable.

Here another variant on your example, using justValue instead of having to map over the selected options: https://svelte.dev/repl/9d370fd874934a989dfad60031e2b18f?version=3.55.1

<script>
  window.process = {env: {}} // REPL issue, ignore
  import Select from 'svelte-select'

  export let values = ['one', 'two']

  // mixed type intermediate variable: 
  // initializes as plain values, will be replaced by selected options by <Select />
  let selectedOptions = values

  let options = [
    { value: 'one', label: 'One' },
    { value: 'two', label: 'Two' },
    { value: 'three', label: 'Three' },
  ]
</script>

<Select multiple items={options} bind:value={selectedOptions} bind:justValue={values} />

<p>Selected values: { values || 'none' }</p>
miklereverie commented 11 months ago

Ahh, thanks!

I have to say, it's weird and confusing that the value property of Select can hold two things: initially, you can let it contain the plain value (like justValue), and after initialization Select will turn value into the selected options {value, label}. It's mixing two different things in the same variable.

Would it be possible to make justValue bind two ways instead of being readonly? I'm glad there is a much simpler workaround, but it doesn't feel right to need this mixed type intermediate variable.

Here another variant on your example, using justValue instead of having to map over the selected options: https://svelte.dev/repl/9d370fd874934a989dfad60031e2b18f?version=3.55.1

<script>
  window.process = {env: {}} // REPL issue, ignore
  import Select from 'svelte-select'

  export let values = ['one', 'two']

  // mixed type intermediate variable: 
  // initializes as plain values, will be replaced by selected options by <Select />
  let selectedOptions = values

  let options = [
    { value: 'one', label: 'One' },
    { value: 'two', label: 'Two' },
    { value: 'three', label: 'Three' },
  ]
</script>

<Select multiple items={options} bind:value={selectedOptions} bind:justValue={values} />

<p>Selected values: { values || 'none' }</p>

Hey! @josdejong sorry to bother you, have you got this working with multiple instances of autocomplete in a single form? I'm having trouble getting and storing the values from multiple autocompletes and sending them to a sveltekit form action. Any help would be greatly appreciated!

josdejong commented 11 months ago

@miklereverie I indeed have a form with multiple instances of Select, you can see it here: TransformWizard.svelte. In the end I was not able to utilize justValue, so I ended up using bind:value, storing the selected value (being an array with strings) in a temporary variable, and have a handler that listens for changes in this temporary variable and applies it to the "real" state. To prevent infinite loops, I have added an equality check on the selected path (array) inside this listener.

miklereverie commented 11 months ago

Aaaaah gotcha, thank you so much for providing the example! In the end I ended up doing this: https://svelte.dev/repl/677daa6ceec642c3a13ee641131f798c?version=4.0.5

Also less than ideal, but at least I found a way to be able to pass only the values to the form action, which was my main issue. Now I can get the values from valuesPrueba and send them along with the rest of the formData using use:enhance on my form actions. (if anyone needs an example of how this works just ask and I'll provide a SvelteLab REPL)

I know it might be easier said than done but I think it would really benefit from having a boolean type property like onlyValue which determines if you either get an object or just the select's value. Here's hoping for that feature on a future version!

Not complaining though, I've tried many different svelte libraries for autocomplete and this one is the best one/better mantained I've tried so far. So thanks @rob-balfre for the amazing job anyways!