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

Two-way binding + reactive declaration behaviour differs between objects and strings #4613

Closed
scottohara opened this issue Mar 30, 2020 · 4 comments

Comments

@scottohara
Copy link

I think this might be a bug, but I'm not sure. It could be by design.

I'm attempting to build a "ComboBox" (a.k.a "typeahead" or "autocomplete") component.

The kind of component where a user can start typing into an input field, and a list of matches/suggestions appears.

The user may continue typing, or they may choose a suggestion from the list.

The component has internal state (value) that can potentially update in two ways, either:

  1. directly by user input (typing), or
  2. indirectly by selecting an item from the list

To account for this, I initially tried having value be both a two-way binding and a reactive declaration, e.g.

<script>
  let fruits = [
    "Apple",
    "Banana",
    "Grapes"
  ];

  let selectedFruit = fruits[0];

  // When selected fruit changes, value should update
  $: value = selectedFruit;
</script>

<input type="text" bind:value>
<ul>
{#each fruits as fruit}
  <li on:click={() => selectedFruit = fruit}>{fruit}</li>
{/each}
</ul>

This works fine. You can type anything into the input field, but if you click an item in the list, the input value updates to the clicked item.

REPL: https://svelte.dev/repl/468ccba36afa48b29525581ad2694d2b?version=3.20.1


Now let's make three small changes:

  1. change the fruits array from simple string values to objects
  2. append .name to the reactive declaration (value = selectedFruit.name)
  3. append .name to the text content of the <li>
<script>
  let fruits = [
    { id: 1, name: "Apple" },
    { id: 2, name: "Banana" },
    { id: 3, name :"Grapes" }
  ];

  let selectedFruit = fruits[0];

  // When selected fruit changes, value should update
  $: value = selectedFruit.name;
</script>

<input type="text" bind:value>
<ul>
{#each fruits as fruit}
  <li on:click={() => selectedFruit = fruit}>{fruit.name}</li>
{/each}
</ul>

It is now no longer possible to type into the input field. It seems that the reactive declaration keeps resetting the value back immediately.

REPL: https://svelte.dev/repl/9f48844e524b4b97a3865453437edc07?version=3.20.1


Finally, lets change from a two-way binding to a one-way binding plus an on:input handler:

<input type="text" value={value} on:input={e => value = e.target.value}>

...and now everything works again.

https://svelte.dev/repl/78f80085c7ec420ebde425c555326ef2?version=3.20.1


I'm interested in understanding why:

  1. the first REPL works with a simple array of strings, but the second one fails with an array of objects
  2. why the second REPL with two-way binding fails, but the third REPL with one-way binding + on:input handler works
@antony
Copy link
Member

antony commented Apr 1, 2020

I can't answer your questions specifically, but I can tell you that binding to a reactive declaration isn't a supported way of doing things. Reactive declarations are supposed to be read-only. I'm not surprised that it doesn't work predictably.

There's quite a nice autocomplete component here: http://simple-svelte-autocomplete.surge.sh/ which might be worth studying.

@colllin
Copy link

colllin commented Apr 17, 2020

I fixed your example: https://svelte.dev/repl/534107e6df1f44bc9ca5b243a0252682?version=3.20.1

The event is probably cleaner, but I prefer to handle changing data with reactivity rather than events. Note that you also had the problem of updating the fruit list itself, which requires an assignment. Since you modified the selectedFruit in-place, you could do fruits = fruits to trigger an update.

I apologize, I misunderstood your example. You just want the input to be editable, so that when the user types in it, you can update the list of fruits (like a search field, not like a tool for renaming fruits 🤦). In that case you can remove the part about renaming fruits and instead update the fruit list / typeahead options.

@pushkine
Copy link
Contributor

duplicate #4448

@pngwn
Copy link
Member

pngwn commented Jun 26, 2021

dupe

@pngwn pngwn closed this as completed Jun 26, 2021
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